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