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