    001/*
    002 * Licensed to the Apache Software Foundation (ASF) under one or more
    003 * contributor license agreements.  See the NOTICE file distributed with
    004 * this work for additional information regarding copyright ownership.
    005 * The ASF licenses this file to You under the Apache License, Version 2.0
    006 * (the "License"); you may not use this file except in compliance with
    007 * the License.  You may obtain a copy of the License at
    008 *
    009 *     http://www.apache.org/licenses/LICENSE-2.0
    010 *
    011 * Unless required by applicable law or agreed to in writing, software
    012 * distributed under the License is distributed on an "AS IS" BASIS,
    013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014 * See the License for the specific language governing permissions and
    015 * limitations under the License.
    016 */
    017package org.apache.commons.configuration2.builder;
    018
    019import java.util.ArrayList;
    020import java.util.Collection;
    021import java.util.HashMap;
    022import java.util.Map;
    023
    024import org.apache.commons.configuration2.ConfigurationDecoder;
    025import org.apache.commons.configuration2.io.ConfigurationLogger;
    026import org.apache.commons.configuration2.beanutils.BeanHelper;
    027import org.apache.commons.configuration2.convert.ConversionHandler;
    028import org.apache.commons.configuration2.convert.ListDelimiterHandler;
    029import org.apache.commons.configuration2.interpol.ConfigurationInterpolator;
    030import org.apache.commons.configuration2.interpol.InterpolatorSpecification;
    031import org.apache.commons.configuration2.interpol.Lookup;
    032import org.apache.commons.configuration2.sync.Synchronizer;
    033
    034/**
    035 * <p>
    036 * An implementation of {@code BuilderParameters} which handles the parameters
    037 * of a {@link ConfigurationBuilder} common to all concrete
    038 * {@code Configuration} implementations.
    039 * </p>
    040 * <p>
    041 * This class provides methods for setting standard properties supported by the
    042 * {@code AbstractConfiguration} base class. A fluent interface can be used to
    043 * set property values.
    044 * </p>
    045 * <p>
    046 * This class is not thread-safe. It is intended that an instance is constructed
    047 * and initialized by a single thread during configuration of a
    048 * {@code ConfigurationBuilder}.
    049 * </p>
    050 *
    051 * @version $Id: BasicBuilderParameters.java 1842194 2018-09-27 22:24:23Z ggregory $
    052 * @since 2.0
    053 */
    054public class BasicBuilderParameters implements Cloneable, BuilderParameters,
    055        BasicBuilderProperties<BasicBuilderParameters>
    056{
    057    /** The key of the <em>throwExceptionOnMissing</em> property. */
    058    private static final String PROP_THROW_EXCEPTION_ON_MISSING =
    059            "throwExceptionOnMissing";
    060
    061    /** The key of the <em>listDelimiterHandler</em> property. */
    062    private static final String PROP_LIST_DELIMITER_HANDLER = "listDelimiterHandler";
    063
    064    /** The key of the <em>logger</em> property. */
    065    private static final String PROP_LOGGER = "logger";
    066
    067    /** The key for the <em>interpolator</em> property. */
    068    private static final String PROP_INTERPOLATOR = "interpolator";
    069
    070    /** The key for the <em>prefixLookups</em> property. */
    071    private static final String PROP_PREFIX_LOOKUPS = "prefixLookups";
    072
    073    /** The key for the <em>defaultLookups</em> property. */
    074    private static final String PROP_DEFAULT_LOOKUPS = "defaultLookups";
    075
    076    /** The key for the <em>parentInterpolator</em> property. */
    077    private static final String PROP_PARENT_INTERPOLATOR = "parentInterpolator";
    078
    079    /** The key for the <em>synchronizer</em> property. */
    080    private static final String PROP_SYNCHRONIZER = "synchronizer";
    081
    082    /** The key for the <em>conversionHandler</em> property. */
    083    private static final String PROP_CONVERSION_HANDLER = "conversionHandler";
    084
    085    /** The key for the <em>configurationDecoder</em> property. */
    086    private static final String PROP_CONFIGURATION_DECODER = "configurationDecoder";
    087
    088    /** The key for the {@code BeanHelper}. */
    089    private static final String PROP_BEAN_HELPER = RESERVED_PARAMETER_PREFIX
    090            + "BeanHelper";
    091
    092    /** The map for storing the current property values. */
    093    private Map<String, Object> properties;
    094
    095    /**
    096     * Creates a new instance of {@code BasicBuilderParameters}.
    097     */
    098    public BasicBuilderParameters()
    099    {
    100        properties = new HashMap<>();
    101    }
    102
    103    /**
    104     * {@inheritDoc} This implementation returns a copy of the internal
    105     * parameters map with the values set so far. Collection structures
    106     * (e.g. for lookup objects) are stored as defensive copies, so the
    107     * original data cannot be modified.
    108     */
    109    @Override
    110    public Map<String, Object> getParameters()
    111    {
    112        final HashMap<String, Object> result =
    113                new HashMap<>(properties);
    114        if (result.containsKey(PROP_INTERPOLATOR))
    115        {
    116            // A custom ConfigurationInterpolator overrides lookups
    117            result.remove(PROP_PREFIX_LOOKUPS);
    118            result.remove(PROP_DEFAULT_LOOKUPS);
    119            result.remove(PROP_PARENT_INTERPOLATOR);
    120        }
    121
    122        createDefensiveCopies(result);
    123        return result;
    124    }
    125
    126    /**
    127     * Sets the <em>logger</em> property. With this property a concrete
    128     * {@code Log} object can be set for the configuration. Thus logging
    129     * behavior can be controlled.
    130     *
    131     * @param log the {@code Log} for the configuration produced by this builder
    132     * @return a reference to this object for method chaining
    133     */
    134    @Override
    135    public BasicBuilderParameters setLogger(final ConfigurationLogger log)
    136    {
    137        return setProperty(PROP_LOGGER, log);
    138    }
    139
    140    /**
    141     * Sets the value of the <em>throwExceptionOnMissing</em> property. This
    142     * property controls the configuration's behavior if missing properties are
    143     * queried: a value of <b>true</b> causes the configuration to throw an
    144     * exception, for a value of <b>false</b> it will return <b>null</b> values.
    145     * (Note: Methods returning a primitive data type will always throw an
    146     * exception if the property is not defined.)
    147     *
    148     * @param b the value of the property
    149     * @return a reference to this object for method chaining
    150     */
    151    @Override
    152    public BasicBuilderParameters setThrowExceptionOnMissing(final boolean b)
    153    {
    154        return setProperty(PROP_THROW_EXCEPTION_ON_MISSING, Boolean.valueOf(b));
    155    }
    156
    157    /**
    158     * Sets the value of the <em>listDelimiterHandler</em> property. This
    159     * property defines the object responsible for dealing with list delimiter
    160     * and escaping characters. Note:
    161     * {@link org.apache.commons.configuration2.AbstractConfiguration AbstractConfiguration}
    162     * does not allow setting this property to <b>null</b>. If the default
    163     * {@code ListDelimiterHandler} is to be used, do not call this method.
    164     *
    165     * @param handler the {@code ListDelimiterHandler}
    166     * @return a reference to this object for method chaining
    167     */
    168    @Override
    169    public BasicBuilderParameters setListDelimiterHandler(
    170            final ListDelimiterHandler handler)
    171    {
    172        return setProperty(PROP_LIST_DELIMITER_HANDLER, handler);
    173    }
    174
    175    /**
    176     * {@inheritDoc} The passed in {@code ConfigurationInterpolator} is set
    177     * without modifications.
    178     */
    179    @Override
    180    public BasicBuilderParameters setInterpolator(final ConfigurationInterpolator ci)
    181    {
    182        return setProperty(PROP_INTERPOLATOR, ci);
    183    }
    184
    185    /**
    186     * {@inheritDoc} A defensive copy of the passed in map is created. A
    187     * <b>null</b> argument causes all prefix lookups to be removed from the
    188     * internal parameters map.
    189     */
    190    @Override
    191    public BasicBuilderParameters setPrefixLookups(
    192            final Map<String, ? extends Lookup> lookups)
    193    {
    194        if (lookups == null)
    195        {
    196            properties.remove(PROP_PREFIX_LOOKUPS);
    197            return this;
    198        }
    199        return setProperty(PROP_PREFIX_LOOKUPS,
    200                new HashMap<>(lookups));
    201    }
    202
    203    /**
    204     * {@inheritDoc} A defensive copy of the passed in collection is created. A
    205     * <b>null</b> argument causes all default lookups to be removed from the
    206     * internal parameters map.
    207     */
    208    @Override
    209    public BasicBuilderParameters setDefaultLookups(
    210            final Collection<? extends Lookup> lookups)
    211    {
    212        if (lookups == null)
    213        {
    214            properties.remove(PROP_DEFAULT_LOOKUPS);
    215            return this;
    216        }
    217        return setProperty(PROP_DEFAULT_LOOKUPS, new ArrayList<>(
    218                lookups));
    219    }
    220
    221    /**
    222     * {@inheritDoc} This implementation stores the passed in
    223     * {@code ConfigurationInterpolator} object in the internal parameters map.
    224     */
    225    @Override
    226    public BasicBuilderParameters setParentInterpolator(
    227            final ConfigurationInterpolator parent)
    228    {
    229        return setProperty(PROP_PARENT_INTERPOLATOR, parent);
    230    }
    231
    232    /**
    233     * {@inheritDoc} This implementation stores the passed in
    234     * {@code Synchronizer} object in the internal parameters map.
    235     */
    236    @Override
    237    public BasicBuilderParameters setSynchronizer(final Synchronizer sync)
    238    {
    239        return setProperty(PROP_SYNCHRONIZER, sync);
    240    }
    241
    242    /**
    243     * {@inheritDoc} This implementation stores the passed in
    244     * {@code ConversionHandler} object in the internal parameters map.
    245     */
    246    @Override
    247    public BasicBuilderParameters setConversionHandler(final ConversionHandler handler)
    248    {
    249        return setProperty(PROP_CONVERSION_HANDLER, handler);
    250    }
    251
    252    /**
    253     * {@inheritDoc} This implementation stores the passed in {@code BeanHelper}
    254     * object in the internal parameters map, but uses a reserved key, so that
    255     * it is not used for the initialization of properties of the managed
    256     * configuration object. The {@code fetchBeanHelper()} method can be used to
    257     * obtain the {@code BeanHelper} instance from a parameters map.
    258     */
    259    @Override
    260    public BasicBuilderParameters setBeanHelper(final BeanHelper beanHelper)
    261    {
    262        return setProperty(PROP_BEAN_HELPER, beanHelper);
    263    }
    264
    265    /**
    266     * {@inheritDoc} This implementation stores the passed in
    267     * {@code ConfigurationDecoder} object in the internal parameters map.
    268     */
    269    @Override
    270    public BasicBuilderParameters setConfigurationDecoder(
    271            final ConfigurationDecoder decoder)
    272    {
    273        return setProperty(PROP_CONFIGURATION_DECODER, decoder);
    274    }
    275
    276    /**
    277     * Merges this object with the given parameters object. This method adds all
    278     * property values defined by the passed in parameters object to the
    279     * internal storage which are not already in. So properties already defined
    280     * in this object take precedence. Property names starting with the reserved
    281     * parameter prefix are ignored.
    282     *
    283     * @param p the object whose properties should be merged (must not be
    284     *        <b>null</b>)
    285     * @throws IllegalArgumentException if the passed in object is <b>null</b>
    286     */
    287    public void merge(final BuilderParameters p)
    288    {
    289        if (p == null)
    290        {
    291            throw new IllegalArgumentException(
    292                    "Parameters to merge must not be null!");
    293        }
    294
    295        for (final Map.Entry<String, Object> e : p.getParameters().entrySet())
    296        {
    297            if (!properties.containsKey(e.getKey())
    298                    && !e.getKey().startsWith(RESERVED_PARAMETER_PREFIX))
    299            {
    300                storeProperty(e.getKey(), e.getValue());
    301            }
    302        }
    303    }
    304
    305    /**
    306     * Inherits properties from the specified map. This can be used for instance
    307     * to reuse parameters from one builder in another builder - also in
    308     * parent-child relations in which a parent builder creates child builders.
    309     * The purpose of this method is to let a concrete implementation decide
    310     * which properties can be inherited. Because parameters are basically
    311     * organized as a map it would be possible to simply copy over all
    312     * properties from the source object. However, this is not appropriate in
    313     * all cases. For instance, some properties - like a
    314     * {@code ConfigurationInterpolator} - are tightly connected to a
    315     * configuration and cannot be reused in a different context. For other
    316     * properties, e.g. a file name, it does not make sense to copy it.
    317     * Therefore, an implementation has to be explicit in the properties it
    318     * wants to take over.
    319     *
    320     * @param source the source properties to inherit from
    321     * @throws IllegalArgumentException if the source map is <b>null</b>
    322     */
    323    public void inheritFrom(final Map<String, ?> source)
    324    {
    325        if (source == null)
    326        {
    327            throw new IllegalArgumentException(
    328                    "Source properties must not be null!");
    329        }
    330        copyPropertiesFrom(source, PROP_BEAN_HELPER, PROP_CONFIGURATION_DECODER,
    331                PROP_CONVERSION_HANDLER, PROP_LIST_DELIMITER_HANDLER,
    332                PROP_LOGGER, PROP_SYNCHRONIZER,
    333                PROP_THROW_EXCEPTION_ON_MISSING);
    334    }
    335
    336    /**
    337     * Obtains a specification for a {@link ConfigurationInterpolator} from the
    338     * specified map with parameters. All properties related to interpolation
    339     * are evaluated and added to the specification object.
    340     *
    341     * @param params the map with parameters (must not be <b>null</b>)
    342     * @return an {@code InterpolatorSpecification} object constructed with data
    343     *         from the map
    344     * @throws IllegalArgumentException if the map is <b>null</b> or contains
    345     *         invalid data
    346     */
    347    public static InterpolatorSpecification fetchInterpolatorSpecification(
    348            final Map<String, Object> params)
    349    {
    350        checkParameters(params);
    351        return new InterpolatorSpecification.Builder()
    352                .withInterpolator(
    353                        fetchParameter(params, PROP_INTERPOLATOR,
    354                                ConfigurationInterpolator.class))
    355                .withParentInterpolator(
    356                        fetchParameter(params, PROP_PARENT_INTERPOLATOR,
    357                                ConfigurationInterpolator.class))
    358                .withPrefixLookups(fetchAndCheckPrefixLookups(params))
    359                .withDefaultLookups(fetchAndCheckDefaultLookups(params))
    360                .create();
    361    }
    362
    363    /**
    364     * Obtains the {@code BeanHelper} object from the specified map with
    365     * parameters. This method can be used to obtain an instance from a
    366     * parameters map that has been set via the {@code setBeanHelper()} method.
    367     * If no such instance is found, result is <b>null</b>.
    368     *
    369     * @param params the map with parameters (must not be <b>null</b>)
    370     * @return the {@code BeanHelper} stored in this map or <b>null</b>
    371     * @throws IllegalArgumentException if the map is <b>null</b>
    372     */
    373    public static BeanHelper fetchBeanHelper(final Map<String, Object> params)
    374    {
    375        checkParameters(params);
    376        return (BeanHelper) params.get(PROP_BEAN_HELPER);
    377    }
    378
    379    /**
    380     * Clones this object. This is useful because multiple builder instances may
    381     * use a similar set of parameters. However, single instances of parameter
    382     * objects must not assigned to multiple builders. Therefore, cloning a
    383     * parameters object provides a solution for this use case. This method
    384     * creates a new parameters object with the same content as this one. The
    385     * internal map storing the parameter values is cloned, too, also collection
    386     * structures contained in this map. However, no a full deep clone operation
    387     * is performed. Objects like a {@code ConfigurationInterpolator} or
    388     * {@code Lookup}s are shared between this and the newly created instance.
    389     *
    390     * @return a clone of this object
    391     */
    392    @Override
    393    public BasicBuilderParameters clone()
    394    {
    395        try
    396        {
    397            final BasicBuilderParameters copy =
    398                    (BasicBuilderParameters) super.clone();
    399            copy.properties = getParameters();
    400            return copy;
    401        }
    402        catch (final CloneNotSupportedException cnex)
    403        {
    404            // should not happen
    405            throw new AssertionError(cnex);
    406        }
    407    }
    408
    409    /**
    410     * Sets a property for this parameters object. Properties are stored in an
    411     * internal map. With this method a new entry can be added to this map. If
    412     * the value is <b>null</b>, the key is removed from the internal map. This
    413     * method can be used by sub classes which also store properties in a map.
    414     *
    415     * @param key the key of the property
    416     * @param value the value of the property
    417     */
    418    protected void storeProperty(final String key, final Object value)
    419    {
    420        if (value == null)
    421        {
    422            properties.remove(key);
    423        }
    424        else
    425        {
    426            properties.put(key, value);
    427        }
    428    }
    429
    430    /**
    431     * Obtains the value of the specified property from the internal map. This
    432     * method can be used by derived classes if a specific property is to be
    433     * accessed. If the given key is not found, result is <b>null</b>.
    434     *
    435     * @param key the key of the property in question
    436     * @return the value of the property with this key or <b>null</b>
    437     */
    438    protected Object fetchProperty(final String key)
    439    {
    440        return properties.get(key);
    441    }
    442
    443    /**
    444     * Copies a number of properties from the given map into this object.
    445     * Properties are only copied if they are defined in the source map.
    446     *
    447     * @param source the source map
    448     * @param keys the keys to be copied
    449     */
    450    protected void copyPropertiesFrom(final Map<String, ?> source, final String... keys)
    451    {
    452        for (final String key : keys)
    453        {
    454            final Object value = source.get(key);
    455            if (value != null)
    456            {
    457                storeProperty(key, value);
    458            }
    459        }
    460    }
    461
    462    /**
    463     * Helper method for setting a property value.
    464     *
    465     * @param key the key of the property
    466     * @param value the value of the property
    467     * @return a reference to this object
    468     */
    469    private BasicBuilderParameters setProperty(final String key, final Object value)
    470    {
    471        storeProperty(key, value);
    472        return this;
    473    }
    474
    475    /**
    476     * Creates defensive copies for collection structures when constructing the
    477     * map with parameters. It should not be possible to modify this object's
    478     * internal state when having access to the parameters map.
    479     *
    480     * @param params the map with parameters to be passed to the caller
    481     */
    482    private static void createDefensiveCopies(final HashMap<String, Object> params)
    483    {
    484        final Map<String, ? extends Lookup> prefixLookups =
    485                fetchPrefixLookups(params);
    486        if (prefixLookups != null)
    487        {
    488            params.put(PROP_PREFIX_LOOKUPS, new HashMap<>(
    489                    prefixLookups));
    490        }
    491        final Collection<? extends Lookup> defLookups = fetchDefaultLookups(params);
    492        if (defLookups != null)
    493        {
    494            params.put(PROP_DEFAULT_LOOKUPS, new ArrayList<>(defLookups));
    495        }
    496    }
    497
    498    /**
    499     * Obtains the map with prefix lookups from the parameters map.
    500     *
    501     * @param params the map with parameters
    502     * @return the map with prefix lookups (may be <b>null</b>)
    503     */
    504    private static Map<String, ? extends Lookup> fetchPrefixLookups(
    505            final Map<String, Object> params)
    506    {
    507        // This is safe to cast because we either have full control over the map
    508        // and thus know the types of the contained values or have checked
    509        // the content before
    510        @SuppressWarnings("unchecked")
    511        final
    512        Map<String, ? extends Lookup> prefixLookups =
    513                (Map<String, ? extends Lookup>) params.get(PROP_PREFIX_LOOKUPS);
    514        return prefixLookups;
    515    }
    516
    517    /**
    518     * Tests whether the passed in map with parameters contains a map with
    519     * prefix lookups. This method is used if the parameters map is from an
    520     * insecure source and we cannot be sure that it contains valid data.
    521     * Therefore, we have to map that the key for the prefix lookups actually
    522     * points to a map containing keys and values of expected data types.
    523     *
    524     * @param params the parameters map
    525     * @return the obtained map with prefix lookups
    526     * @throws IllegalArgumentException if the map contains invalid data
    527     */
    528    private static Map<String, ? extends Lookup> fetchAndCheckPrefixLookups(
    529            final Map<String, Object> params)
    530    {
    531        final Map<?, ?> prefixes =
    532                fetchParameter(params, PROP_PREFIX_LOOKUPS, Map.class);
    533        if (prefixes == null)
    534        {
    535            return null;
    536        }
    537
    538        for (final Map.Entry<?, ?> e : prefixes.entrySet())
    539        {
    540            if (!(e.getKey() instanceof String)
    541                    || !(e.getValue() instanceof Lookup))
    542            {
    543                throw new IllegalArgumentException(
    544                        "Map with prefix lookups contains invalid data: "
    545                                + prefixes);
    546            }
    547        }
    548        return fetchPrefixLookups(params);
    549    }
    550
    551    /**
    552     * Obtains the collection with default lookups from the parameters map.
    553     *
    554     * @param params the map with parameters
    555     * @return the collection with default lookups (may be <b>null</b>)
    556     */
    557    private static Collection<? extends Lookup> fetchDefaultLookups(
    558            final Map<String, Object> params)
    559    {
    560        // This is safe to cast because we either have full control over the map
    561        // and thus know the types of the contained values or have checked
    562        // the content before
    563        @SuppressWarnings("unchecked")
    564        final
    565        Collection<? extends Lookup> defLookups =
    566                (Collection<? extends Lookup>) params.get(PROP_DEFAULT_LOOKUPS);
    567        return defLookups;
    568    }
    569
    570    /**
    571     * Tests whether the passed in map with parameters contains a valid
    572     * collection with default lookups. This method works like
    573     * {@link #fetchAndCheckPrefixLookups(Map)}, but tests the default lookups
    574     * collection.
    575     *
    576     * @param params the map with parameters
    577     * @return the collection with default lookups (may be <b>null</b>)
    578     * @throws IllegalArgumentException if invalid data is found
    579     */
    580    private static Collection<? extends Lookup> fetchAndCheckDefaultLookups(
    581            final Map<String, Object> params)
    582    {
    583        final Collection<?> col =
    584                fetchParameter(params, PROP_DEFAULT_LOOKUPS, Collection.class);
    585        if (col == null)
    586        {
    587            return null;
    588        }
    589
    590        for (final Object o : col)
    591        {
    592            if (!(o instanceof Lookup))
    593            {
    594                throw new IllegalArgumentException(
    595                        "Collection with default lookups contains invalid data: "
    596                                + col);
    597            }
    598        }
    599        return fetchDefaultLookups(params);
    600    }
    601
    602    /**
    603     * Obtains a parameter from a map and performs a type check.
    604     *
    605     * @param params the map with parameters
    606     * @param key the key of the parameter
    607     * @param expClass the expected class of the parameter value
    608     * @param <T> the parameter type
    609     * @return the value of the parameter in the correct data type
    610     * @throws IllegalArgumentException if the parameter is not of the expected
    611     *         type
    612     */
    613    private static <T> T fetchParameter(final Map<String, Object> params, final String key,
    614            final Class<T> expClass)
    615    {
    616        final Object value = params.get(key);
    617        if (value == null)
    618        {
    619            return null;
    620        }
    621        if (!expClass.isInstance(value))
    622        {
    623            throw new IllegalArgumentException(String.format(
    624                    "Parameter %s is not of type %s!", key,
    625                    expClass.getSimpleName()));
    626        }
    627        return expClass.cast(value);
    628    }
    629
    630    /**
    631     * Checks whether a map with parameters is present. Throws an exception if
    632     * not.
    633     *
    634     * @param params the map with parameters to check
    635     * @throws IllegalArgumentException if the map is <b>null</b>
    636     */
    637    private static void checkParameters(final Map<String, Object> params)
    638    {
    639        if (params == null)
    640        {
    641            throw new IllegalArgumentException(
    642                    "Parameters map must not be null!");
    643        }
    644    }
    645}