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 */
017
018package org.apache.commons.configuration2;
019
020import java.math.BigDecimal;
021import java.math.BigInteger;
022import java.time.Duration;
023import java.util.ArrayList;
024import java.util.Arrays;
025import java.util.Collection;
026import java.util.Collections;
027import java.util.Iterator;
028import java.util.List;
029import java.util.Map;
030import java.util.NoSuchElementException;
031import java.util.Objects;
032import java.util.Properties;
033import java.util.concurrent.atomic.AtomicReference;
034import java.util.stream.Collectors;
035
036import org.apache.commons.configuration2.convert.ConversionHandler;
037import org.apache.commons.configuration2.convert.DefaultConversionHandler;
038import org.apache.commons.configuration2.convert.DisabledListDelimiterHandler;
039import org.apache.commons.configuration2.convert.ListDelimiterHandler;
040import org.apache.commons.configuration2.event.BaseEventSource;
041import org.apache.commons.configuration2.event.ConfigurationErrorEvent;
042import org.apache.commons.configuration2.event.ConfigurationEvent;
043import org.apache.commons.configuration2.event.EventListener;
044import org.apache.commons.configuration2.ex.ConversionException;
045import org.apache.commons.configuration2.interpol.ConfigurationInterpolator;
046import org.apache.commons.configuration2.interpol.InterpolatorSpecification;
047import org.apache.commons.configuration2.interpol.Lookup;
048import org.apache.commons.configuration2.io.ConfigurationLogger;
049import org.apache.commons.configuration2.sync.LockMode;
050import org.apache.commons.configuration2.sync.NoOpSynchronizer;
051import org.apache.commons.configuration2.sync.Synchronizer;
052import org.apache.commons.lang3.ArrayUtils;
053import org.apache.commons.lang3.ClassUtils;
054import org.apache.commons.lang3.ObjectUtils;
055import org.apache.commons.lang3.StringUtils;
056import org.apache.commons.lang3.function.FailableRunnable;
057import org.apache.commons.lang3.function.FailableSupplier;
058
059/**
060 * <p>
061 * Abstract configuration class. Provides basic functionality but does not store any data.
062 * </p>
063 * <p>
064 * If you want to write your own Configuration class then you should implement only abstract methods from this class. A
065 * lot of functionality needed by typical implementations of the {@code Configuration} interface is already provided by
066 * this base class. Following is a list of features implemented here:
067 * </p>
068 * <ul>
069 * <li>Data conversion support. The various data types required by the {@code Configuration} interface are already
070 * handled by this base class. A concrete sub class only needs to provide a generic {@code getProperty()} method.</li>
071 * <li>Support for variable interpolation. Property values containing special variable tokens (like {@code ${var}}) will
072 * be replaced by their corresponding values.</li>
073 * <li>Optional support for string lists. The values of properties to be added to this configuration are checked whether
074 * they contain a list delimiter character. If this is the case and if list splitting is enabled, the string is split
075 * and multiple values are added for this property. List splitting is controlled by a {@link ListDelimiterHandler}
076 * object which can be set using the {@link #setListDelimiterHandler(ListDelimiterHandler)} method. It is disabled per
077 * default. To enable this feature, set a suitable {@code ListDelimiterHandler}, for example an instance of
078 * {@link org.apache.commons.configuration2.convert.DefaultListDelimiterHandler DefaultListDelimiterHandler} configured
079 * with the desired list delimiter character.</li>
080 * <li>Allows specifying how missing properties are treated. Per default the get methods returning an object will return
081 * <strong>null</strong> if the searched property key is not found (and no default value is provided). With the
082 * {@code setThrowExceptionOnMissing()} method this behavior can be changed to throw an exception when a requested
083 * property cannot be found.</li>
084 * <li>Basic event support. Whenever this configuration is modified registered event listeners are notified. Refer to
085 * the various {@code EVENT_XXX} constants to get an impression about which event types are supported.</li>
086 * <li>Support for proper synchronization based on the {@link Synchronizer} interface.</li>
087 * </ul>
088 * <p>
089 * Most methods defined by the {@code Configuration} interface are already implemented in this class. Many method
090 * implementations perform basic book-keeping tasks (for example firing events, handling synchronization), and then delegate to
091 * other (protected) methods executing the actual work. Subclasses override these protected methods to define or adapt
092 * behavior. The public entry point methods are final to prevent subclasses from breaking basic functionality.
093 * </p>
094 */
095public abstract class AbstractConfiguration extends BaseEventSource implements Configuration {
096
097    /**
098     * Default configuration delimiter for properties and keys.
099     */
100    static final String DELIMITER = ".";
101
102    /**
103     * Checks an object provided as default value for the {@code getArray()} method. Throws an exception if this is not an
104     * array with the correct component type.
105     *
106     * @param cls the component class for the array
107     * @param defaultValue the default value object to be checked
108     * @throws IllegalArgumentException if this is not a valid default object
109     */
110    private static void checkDefaultValueArray(final Class<?> cls, final Object defaultValue) {
111        if (defaultValue != null && (!defaultValue.getClass().isArray() || !cls.isAssignableFrom(defaultValue.getClass().getComponentType()))) {
112            throw new IllegalArgumentException(
113                "The type of the default value (" + defaultValue.getClass() + ")" + " is not an array of the specified class (" + cls + ")");
114        }
115    }
116
117    /**
118     * Checks whether the specified value is <strong>null</strong> and throws an exception in this case. This method is used by
119     * conversion methods returning primitive Java types. Here values to be returned must not be <strong>null</strong>.
120     *
121     * @param <T> the type of the object to be checked
122     * @param key the key which caused the problem
123     * @param value the value to be checked
124     * @return the passed in value for chaining this method call
125     * @throws NoSuchElementException if the value is <strong>null</strong>
126     */
127    private static <T> T checkNonNullValue(final String key, final T value) {
128        if (value == null) {
129            throwMissingPropertyException(key);
130        }
131        return value;
132    }
133
134    /**
135     * Finds a {@code ConfigurationLookup} pointing to the specified configuration in the default lookups for the specified
136     * {@code ConfigurationInterpolator}.
137     *
138     * @param ci the {@code ConfigurationInterpolator} in question
139     * @param targetConf the target configuration of the searched lookup
140     * @return the found {@code Lookup} object or <strong>null</strong>
141     */
142    private static Lookup findConfigurationLookup(final ConfigurationInterpolator ci, final ImmutableConfiguration targetConf) {
143        for (final Lookup l : ci.getDefaultLookups()) {
144            if (l instanceof ConfigurationLookup && targetConf == ((ConfigurationLookup) l).getConfiguration()) {
145                return l;
146            }
147        }
148        return null;
149    }
150
151    /**
152     * Handles the default collection for a collection conversion. This method fills the target collection with the content
153     * of the default collection. Both collections may be <strong>null</strong>.
154     *
155     * @param target the target collection
156     * @param defaultValue the default collection
157     * @return the initialized target collection
158     */
159    private static <T> Collection<T> handleDefaultCollection(final Collection<T> target, final Collection<T> defaultValue) {
160        if (defaultValue == null) {
161            return null;
162        }
163
164        final Collection<T> result;
165        if (target == null) {
166            result = new ArrayList<>(defaultValue);
167        } else {
168            target.addAll(defaultValue);
169            result = target;
170        }
171        return result;
172    }
173
174    /**
175     * Helper method for throwing an exception for a key that does not map to an existing object.
176     *
177     * @param key the key (to be part of the error message)
178     */
179    private static void throwMissingPropertyException(final String key) {
180        throw new NoSuchElementException(String.format("Key '%s' does not map to an existing object!", key));
181    }
182
183    /** The list delimiter handler. */
184    private ListDelimiterHandler listDelimiterHandler;
185
186    /** The conversion handler. */
187    private ConversionHandler conversionHandler;
188
189    /**
190     * Whether the configuration should throw NoSuchElementExceptions or simply return null when a property does not exist.
191     * Defaults to return null.
192     */
193    private boolean throwExceptionOnMissing;
194
195    /** Stores a reference to the object that handles variable interpolation. */
196    private AtomicReference<ConfigurationInterpolator> interpolator;
197
198    /** The object responsible for synchronization. */
199    private volatile Synchronizer synchronizer = NoOpSynchronizer.INSTANCE;
200
201    /** The object used for dealing with encoded property values. */
202    private ConfigurationDecoder configurationDecoder;
203
204    /** Stores the logger. */
205    private ConfigurationLogger log;
206
207    /**
208     * Creates a new instance of {@code AbstractConfiguration}.
209     */
210    public AbstractConfiguration() {
211        interpolator = new AtomicReference<>();
212        initLogger(null);
213        installDefaultInterpolator();
214        listDelimiterHandler = DisabledListDelimiterHandler.INSTANCE;
215        conversionHandler = DefaultConversionHandler.INSTANCE;
216    }
217
218    /**
219     * Adds a special {@link EventListener} object to this configuration that will log all internal errors. This method is
220     * intended to be used by certain derived classes, for which it is known that they can fail on property access (for example
221     * {@code DatabaseConfiguration}).
222     *
223     * @since 1.4
224     */
225    public final void addErrorLogListener() {
226        addEventListener(ConfigurationErrorEvent.ANY, event -> getLogger().warn("Internal error", event.getCause()));
227    }
228
229    @Override
230    public final void addProperty(final String key, final Object value) {
231        syncWrite(() -> {
232            fireEvent(ConfigurationEvent.ADD_PROPERTY, key, value, true);
233            addPropertyInternal(key, value);
234            fireEvent(ConfigurationEvent.ADD_PROPERTY, key, value, false);
235        }, false);
236    }
237
238    /**
239     * Adds a key/value pair to the Configuration. Override this method to provide write access to underlying Configuration
240     * store.
241     *
242     * @param key key to use for mapping
243     * @param value object to store
244     */
245    protected abstract void addPropertyDirect(String key, Object value);
246
247    /**
248     * Actually adds a property to this configuration. This method is called by {@code addProperty()}. It performs list
249     * splitting if necessary and delegates to {@link #addPropertyDirect(String, Object)} for every single property value.
250     *
251     * @param key the key of the property to be added
252     * @param value the new property value
253     * @since 2.0
254     */
255    protected void addPropertyInternal(final String key, final Object value) {
256        getListDelimiterHandler().parse(value).forEach(obj -> addPropertyDirect(key, obj));
257    }
258
259    /**
260     * Appends the content of the specified configuration to this configuration. The values of all properties contained in
261     * the specified configuration will be appended to this configuration. So if a property is already present in this
262     * configuration, its new value will be a union of the values in both configurations. <em>Note:</em> This method won't
263     * work well when appending hierarchical configurations because it is not able to copy information about the properties'
264     * structure (i.e. the parent-child-relationships will get lost). So when dealing with hierarchical configuration
265     * objects their {@link BaseHierarchicalConfiguration#clone() clone()} methods should be used.
266     *
267     * @param configuration the configuration to be appended (can be <strong>null</strong>, then this operation will have no effect)
268     * @since 1.5
269     */
270    public void append(final Configuration configuration) {
271        if (configuration != null) {
272            configuration.lock(LockMode.READ);
273            try {
274                configuration.getKeys().forEachRemaining(key -> addProperty(key, encodeForCopy(configuration.getProperty(key))));
275            } finally {
276                configuration.unlock(LockMode.READ);
277            }
278        }
279    }
280
281    /**
282     * Notifies this configuration's {@link Synchronizer} that a read operation is about to start. This method is called by
283     * all methods which access this configuration in a read-only mode. Subclasses may override it to perform additional
284     * actions before this read operation. The boolean <em>optimize</em> argument can be evaluated by overridden methods in
285     * derived classes. Some operations which require a lock do not need a fully initialized configuration object. By
286     * setting this flag to <strong>true</strong>, such operations can give a corresponding hint. An overridden
287     * implementation of {@code beginRead()} can then decide to skip some initialization steps. All basic operations in this
288     * class (and most of the basic {@code Configuration} implementations) call this method with a parameter value of
289     * <strong>false</strong>. <strong>In any case the inherited method must be called! Otherwise, proper synchronization is
290     * not guaranteed.</strong>
291     *
292     * @param optimize a flag whether optimization can be performed
293     * @since 2.0
294     */
295    protected void beginRead(final boolean optimize) {
296        getSynchronizer().beginRead();
297    }
298
299    /**
300     * Notifies this configuration's {@link Synchronizer} that an update operation is about to start. This method is called
301     * by all methods which modify this configuration. Subclasses may override it to perform additional operations before an
302     * update. For a description of the boolean <em>optimize</em> argument refer to the documentation of
303     * {@code beginRead()}. <strong>In any case the inherited method must be called! Otherwise, proper synchronization is
304     * not guaranteed.</strong>
305     *
306     * @param optimize a flag whether optimization can be performed
307     * @see #beginRead(boolean)
308     * @since 2.0
309     */
310    protected void beginWrite(final boolean optimize) {
311        getSynchronizer().beginWrite();
312    }
313
314    @Override
315    public final void clear() {
316        syncWrite(() -> {
317            fireEvent(ConfigurationEvent.CLEAR, null, null, true);
318            clearInternal();
319            fireEvent(ConfigurationEvent.CLEAR, null, null, false);
320        }, false);
321    }
322
323    /**
324     * Clears the whole configuration. This method is called by {@code clear()} after some preparations have been made. This
325     * base implementation uses the iterator provided by {@code getKeys()} to remove every single property. Subclasses
326     * should override this method if there is a more efficient way of clearing the configuration.
327     */
328    protected void clearInternal() {
329        setDetailEvents(false);
330        boolean useIterator = true;
331        try {
332            final Iterator<String> it = getKeys();
333            while (it.hasNext()) {
334                final String key = it.next();
335                if (useIterator) {
336                    try {
337                        it.remove();
338                    } catch (final UnsupportedOperationException usoex) {
339                        useIterator = false;
340                    }
341                }
342
343                if (useIterator && containsKey(key)) {
344                    useIterator = false;
345                }
346
347                if (!useIterator) {
348                    // workaround for Iterators that do not remove the
349                    // property
350                    // on calling remove() or do not support remove() at all
351                    clearProperty(key);
352                }
353            }
354        } finally {
355            setDetailEvents(true);
356        }
357    }
358
359    /**
360     * Removes the specified property from this configuration. This implementation performs some preparations and then
361     * delegates to {@code clearPropertyDirect()}, which will do the real work.
362     *
363     * @param key the key to be removed
364     */
365    @Override
366    public final void clearProperty(final String key) {
367        syncWrite(() -> {
368            fireEvent(ConfigurationEvent.CLEAR_PROPERTY, key, null, true);
369            clearPropertyDirect(key);
370            fireEvent(ConfigurationEvent.CLEAR_PROPERTY, key, null, false);
371        }, false);
372    }
373
374    /**
375     * Removes the specified property from this configuration. This method is called by {@code clearProperty()} after it has
376     * done some preparations. It must be overridden in sub classes.
377     *
378     * @param key the key to be removed
379     */
380    protected abstract void clearPropertyDirect(String key);
381
382    /**
383     * Creates a clone of the {@code ConfigurationInterpolator} used by this instance. This method can be called by
384     * {@code clone()} implementations of derived classes. Normally, the {@code ConfigurationInterpolator} of a
385     * configuration instance must not be shared with other instances because it contains a specific {@code Lookup} object
386     * pointing to the owning configuration. This has to be taken into account when cloning a configuration. This method
387     * creates a new {@code ConfigurationInterpolator} for this configuration instance which contains all lookup objects
388     * from the original {@code ConfigurationInterpolator} except for the configuration specific lookup pointing to the
389     * passed in original configuration. This one is replaced by a corresponding {@code Lookup} referring to this
390     * configuration.
391     *
392     * @param orgConfig the original configuration from which this one was cloned
393     * @since 2.0
394     */
395    protected void cloneInterpolator(final AbstractConfiguration orgConfig) {
396        interpolator = new AtomicReference<>();
397        final ConfigurationInterpolator orgInterpolator = orgConfig.getInterpolator();
398        final List<Lookup> defaultLookups = orgInterpolator.getDefaultLookups();
399        final Lookup lookup = findConfigurationLookup(orgInterpolator, orgConfig);
400        if (lookup != null) {
401            defaultLookups.remove(lookup);
402        }
403
404        installInterpolator(orgInterpolator.getLookups(), defaultLookups);
405    }
406
407    /**
408     * Checks if the specified value exists in the properties structure mapped by the provided keys.
409     *
410     * @param keys an Iterator of String keys to search for the value
411     * @param value the String value to search for in the properties
412     * @return true if the value is found in the properties, false otherwise
413     * @since 2.11.0
414     */
415    protected boolean contains(final Iterator<String> keys, final Object value) {
416        while (keys.hasNext()) {
417            if (Objects.equals(value, getProperty(keys.next()))) {
418                return true;
419            }
420        }
421        return false;
422    }
423
424    /**
425     * {@inheritDoc} This implementation handles synchronization and delegates to {@code containsKeyInternal()}.
426     */
427    @Override
428    public final boolean containsKey(final String key) {
429        return syncRead(() -> containsKeyInternal(key), false);
430    }
431
432    /**
433     * Actually checks whether the specified key is contained in this configuration. This method is called by
434     * {@code containsKey()}. It has to be defined by concrete subclasses.
435     *
436     * @param key the key in question
437     * @return <strong>true</strong> if this key is contained in this configuration, <strong>false</strong> otherwise
438     * @since 2.0
439     */
440    protected abstract boolean containsKeyInternal(String key);
441
442    /**
443     * {@inheritDoc} This implementation handles synchronization and delegates to {@code containsKeyInternal()}.
444     * @since 2.11.0
445     */
446    @Override
447    public final boolean containsValue(final Object value) {
448        return syncRead(() -> containsValueInternal(value), false);
449    }
450
451    /**
452     * Tests whether this configuration contains one or more matches to this value. This operation stops at first match but may be more expensive than the
453     * {@link #containsKeyInternal containsKey} method.
454     * <p>
455     * The implementation of this method will be different depending on the type of Configuration used.
456     * </p>
457     *
458     * <p>
459     * Note that this method is identical in functionality to {@link #containsValue containsValue}, (which is part of the {@link ImmutableConfiguration}
460     * interface).
461     * </p>
462     *
463     * @param value the value in question
464     * @return {@code true} if and only if some key maps to the {@code value} argument in this configuration as determined by the {@code equals} method;
465     *         {@code false} otherwise.
466     * @since 2.11.0
467     */
468    protected abstract boolean containsValueInternal(Object value);
469
470    /**
471     * Helper method for obtaining a property value with a type conversion.
472     *
473     * @param <T> the target type of the conversion
474     * @param cls the target class
475     * @param key the key of the desired property
476     * @param defValue a default value
477     * @param throwOnMissing a flag whether an exception should be thrown for a missing value
478     * @return the converted value
479     */
480    private <T> T convert(final Class<T> cls, final String key, final T defValue, final boolean throwOnMissing) {
481        if (cls.isArray()) {
482            return cls.cast(convertToArray(cls.getComponentType(), key, defValue));
483        }
484
485        final T result = getAndConvertProperty(cls, key, defValue);
486        if (result == null) {
487            if (throwOnMissing && isThrowExceptionOnMissing()) {
488                throwMissingPropertyException(key);
489            }
490            return defValue;
491        }
492
493        return result;
494    }
495
496    /**
497     * Performs a conversion to an array result class. This implementation delegates to the {@link ConversionHandler} to
498     * perform the actual type conversion. If this results in a <strong>null</strong> result (because the property is undefined), the
499     * default value is returned. It is checked whether the default value is an array with the correct component type. If
500     * not, an exception is thrown.
501     *
502     * @param cls the component class of the array
503     * @param key the configuration key
504     * @param defaultValue an optional default value
505     * @return the converted array
506     * @throws IllegalArgumentException if the default value is not a compatible array
507     */
508    private Object convertToArray(final Class<?> cls, final String key, final Object defaultValue) {
509        checkDefaultValueArray(cls, defaultValue);
510        return ObjectUtils.defaultIfNull(getConversionHandler().toArray(getProperty(key), cls, getInterpolator()), defaultValue);
511    }
512
513    /**
514     * Copies the content of the specified configuration into this configuration. If the specified configuration contains a
515     * key that is also present in this configuration, the value of this key will be replaced by the new value.
516     * <em>Note:</em> This method won't work well when copying hierarchical configurations because it is not able to copy
517     * information about the properties' structure (i.e. the parent-child-relationships will get lost). So when dealing with
518     * hierarchical configuration objects their {@link BaseHierarchicalConfiguration#clone() clone()} methods should be
519     * used.
520     *
521     * @param configuration the configuration to copy (can be <strong>null</strong>, then this operation will have no effect)
522     * @since 1.5
523     */
524    public void copy(final Configuration configuration) {
525        if (configuration != null) {
526            configuration.lock(LockMode.READ);
527            try {
528                configuration.getKeys().forEachRemaining(key -> setProperty(key, encodeForCopy(configuration.getProperty(key))));
529            } finally {
530                configuration.unlock(LockMode.READ);
531            }
532        }
533    }
534
535    /**
536     * Encodes a property value so that it can be added to this configuration. This method deals with list delimiters. The
537     * passed in object has to be escaped so that an add operation yields the same result. If it is a list, all of its
538     * values have to be escaped.
539     *
540     * @param value the value to be encoded
541     * @return the encoded value
542     */
543    private Object encodeForCopy(final Object value) {
544        if (value instanceof Collection) {
545            return encodeListForCopy((Collection<?>) value);
546        }
547        return getListDelimiterHandler().escape(value, ListDelimiterHandler.NOOP_TRANSFORMER);
548    }
549
550    /**
551     * Encodes a list with property values so that it can be added to this configuration. This method calls
552     * {@code encodeForCopy()} for all list elements.
553     *
554     * @param values the list to be encoded
555     * @return a list with encoded elements
556     */
557    private Object encodeListForCopy(final Collection<?> values) {
558        return values.stream().map(this::encodeForCopy).collect(Collectors.toList());
559    }
560
561    /**
562     * Notifies this configuration's {@link Synchronizer} that a read operation has finished. This method is called by all
563     * methods which access this configuration in a read-only manner at the end of their execution. Subclasses may override
564     * it to perform additional actions after this read operation. <strong>In any case the inherited method must be called!
565     * Otherwise, the read lock will not be released.</strong>
566     *
567     * @since 2.0
568     */
569    protected void endRead() {
570        getSynchronizer().endRead();
571    }
572
573    /**
574     * Notifies this configuration's {@link Synchronizer} that an update operation has finished. This method is called by
575     * all methods which modify this configuration at the end of their execution. Subclasses may override it to perform
576     * additional operations after an update. <strong>In any case the inherited method must be called! Otherwise, the write
577     * lock will not be released.</strong>
578     *
579     * @since 2.0
580     */
581    protected void endWrite() {
582        getSynchronizer().endWrite();
583    }
584
585    /**
586     * Finds a {@code ConfigurationLookup} pointing to this configuration in the default lookups of the specified
587     * {@code ConfigurationInterpolator}. This method is called to ensure that there is exactly one default lookup querying
588     * this configuration.
589     *
590     * @param ci the {@code ConfigurationInterpolator} in question
591     * @return the found {@code Lookup} object or <strong>null</strong>
592     */
593    private Lookup findConfigurationLookup(final ConfigurationInterpolator ci) {
594        return findConfigurationLookup(ci, this);
595    }
596
597    @Override
598    public <T> T get(final Class<T> cls, final String key) {
599        return convert(cls, key, null, true);
600    }
601
602    /**
603     * {@inheritDoc} This implementation delegates to the {@link ConversionHandler} to perform the actual type conversion.
604     */
605    @Override
606    public <T> T get(final Class<T> cls, final String key, final T defaultValue) {
607        return convert(cls, key, defaultValue, false);
608    }
609
610    /**
611     * Obtains the property value for the specified key and converts it to the given target class.
612     *
613     * @param <T> the target type of the conversion
614     * @param cls the target class
615     * @param key the key of the desired property
616     * @param defaultValue a default value
617     * @return the converted value of this property
618     * @throws ConversionException if the conversion cannot be performed
619     */
620    private <T> T getAndConvertProperty(final Class<T> cls, final String key, final T defaultValue) {
621        final Object value = getProperty(key);
622        try {
623            return ObjectUtils.defaultIfNull(getConversionHandler().to(value, cls, getInterpolator()), defaultValue);
624        } catch (final ConversionException cex) {
625            // improve error message
626            throw new ConversionException(String.format("Key '%s' cannot be converted to class %s. Value is: '%s'.", key, cls.getName(), String.valueOf(value)),
627                cex.getCause());
628        }
629    }
630
631    @Override
632    public Object getArray(final Class<?> cls, final String key) {
633        return getArray(cls, key, null);
634    }
635
636    /**
637     * {@inheritDoc} This implementation delegates to the {@link ConversionHandler} to perform the actual type conversion.
638     * If this results in a <strong>null</strong> result (because the property is undefined), the default value is returned. It is
639     * checked whether the default value is an array with the correct component type. If not, an exception is thrown.
640     *
641     * @throws IllegalArgumentException if the default value is not a compatible array
642     */
643    @Override
644    public Object getArray(final Class<?> cls, final String key, final Object defaultValue) {
645        return convertToArray(cls, key, defaultValue);
646    }
647
648    /**
649     * {@inheritDoc}
650     *
651     * @see #setThrowExceptionOnMissing(boolean)
652     */
653    @Override
654    public BigDecimal getBigDecimal(final String key) {
655        return convert(BigDecimal.class, key, null, true);
656    }
657
658    @Override
659    public BigDecimal getBigDecimal(final String key, final BigDecimal defaultValue) {
660        return convert(BigDecimal.class, key, defaultValue, false);
661    }
662
663    /**
664     * {@inheritDoc}
665     *
666     * @see #setThrowExceptionOnMissing(boolean)
667     */
668    @Override
669    public BigInteger getBigInteger(final String key) {
670        return convert(BigInteger.class, key, null, true);
671    }
672
673    @Override
674    public BigInteger getBigInteger(final String key, final BigInteger defaultValue) {
675        return convert(BigInteger.class, key, defaultValue, false);
676    }
677
678    @Override
679    public boolean getBoolean(final String key) {
680        final Boolean b = convert(Boolean.class, key, null, true);
681        return checkNonNullValue(key, b).booleanValue();
682    }
683
684    @Override
685    public boolean getBoolean(final String key, final boolean defaultValue) {
686        return getBoolean(key, Boolean.valueOf(defaultValue)).booleanValue();
687    }
688
689    /**
690     * Obtains the value of the specified key and tries to convert it into a {@code Boolean} object. If the property has no
691     * value, the passed in default value will be used.
692     *
693     * @param key the key of the property
694     * @param defaultValue the default value
695     * @return the value of this key converted to a {@code Boolean}
696     * @throws ConversionException if the value cannot be converted to a {@code Boolean}
697     */
698    @Override
699    public Boolean getBoolean(final String key, final Boolean defaultValue) {
700        return convert(Boolean.class, key, defaultValue, false);
701    }
702
703    @Override
704    public byte getByte(final String key) {
705        final Byte b = convert(Byte.class, key, null, true);
706        return checkNonNullValue(key, b).byteValue();
707    }
708
709    @Override
710    public byte getByte(final String key, final byte defaultValue) {
711        return getByte(key, Byte.valueOf(defaultValue)).byteValue();
712    }
713
714    @Override
715    public Byte getByte(final String key, final Byte defaultValue) {
716        return convert(Byte.class, key, defaultValue, false);
717    }
718
719    @Override
720    public <T> Collection<T> getCollection(final Class<T> cls, final String key, final Collection<T> target) {
721        return getCollection(cls, key, target, null);
722    }
723
724    /**
725     * {@inheritDoc} This implementation delegates to the {@link ConversionHandler} to perform the actual conversion. If no
726     * target collection is provided, an {@code ArrayList} is created.
727     */
728    @Override
729    public <T> Collection<T> getCollection(final Class<T> cls, final String key, final Collection<T> target, final Collection<T> defaultValue) {
730        final Object src = getProperty(key);
731        if (src == null) {
732            return handleDefaultCollection(target, defaultValue);
733        }
734
735        final Collection<T> targetCol = target != null ? target : new ArrayList<>();
736        getConversionHandler().toCollection(src, cls, getInterpolator(), targetCol);
737        return targetCol;
738    }
739
740    /**
741     * Gets the {@code ConfigurationDecoder} used by this instance.
742     *
743     * @return the {@code ConfigurationDecoder}
744     * @since 2.0
745     */
746    public ConfigurationDecoder getConfigurationDecoder() {
747        return configurationDecoder;
748    }
749
750    /**
751     * Gets the {@code ConversionHandler} used by this instance.
752     *
753     * @return the {@code ConversionHandler}
754     * @since 2.0
755     */
756    public ConversionHandler getConversionHandler() {
757        return conversionHandler;
758    }
759
760    @Override
761    public double getDouble(final String key) {
762        final Double d = convert(Double.class, key, null, true);
763        return checkNonNullValue(key, d).doubleValue();
764    }
765
766    @Override
767    public double getDouble(final String key, final double defaultValue) {
768        return getDouble(key, Double.valueOf(defaultValue)).doubleValue();
769    }
770
771    @Override
772    public Double getDouble(final String key, final Double defaultValue) {
773        return convert(Double.class, key, defaultValue, false);
774    }
775
776    @Override
777    public Duration getDuration(final String key) {
778        return checkNonNullValue(key, convert(Duration.class, key, null, true));
779    }
780
781    @Override
782    public Duration getDuration(final String key, final Duration defaultValue) {
783        return convert(Duration.class, key, defaultValue, false);
784    }
785
786    /**
787     * {@inheritDoc} This implementation makes use of the {@code ConfigurationDecoder} set for this configuration. If no
788     * such object has been set, an {@code IllegalStateException} exception is thrown.
789     *
790     * @throws IllegalStateException if no {@code ConfigurationDecoder} is set
791     * @see #setConfigurationDecoder(ConfigurationDecoder)
792     */
793    @Override
794    public String getEncodedString(final String key) {
795        final ConfigurationDecoder decoder = getConfigurationDecoder();
796        if (decoder == null) {
797            throw new IllegalStateException("No default ConfigurationDecoder defined!");
798        }
799        return getEncodedString(key, decoder);
800    }
801
802    /**
803     * {@inheritDoc} This implementation delegates to {@link #getString(String)} in order to obtain the value of the passed
804     * in key. This value is passed to the decoder. Because {@code getString()} is used behind the scenes all standard
805     * features like handling of missing keys and interpolation work as expected.
806     */
807    @Override
808    public String getEncodedString(final String key, final ConfigurationDecoder decoder) {
809        if (decoder == null) {
810            throw new IllegalArgumentException("ConfigurationDecoder must not be null!");
811        }
812
813        final String value = getString(key);
814        return value != null ? decoder.decode(value) : null;
815    }
816
817    @Override
818    public float getFloat(final String key) {
819        final Float f = convert(Float.class, key, null, true);
820        return checkNonNullValue(key, f).floatValue();
821    }
822
823    @Override
824    public float getFloat(final String key, final float defaultValue) {
825        return getFloat(key, Float.valueOf(defaultValue)).floatValue();
826    }
827
828    @Override
829    public Float getFloat(final String key, final Float defaultValue) {
830        return convert(Float.class, key, defaultValue, false);
831    }
832
833    @Override
834    public int getInt(final String key) {
835        final Integer i = convert(Integer.class, key, null, true);
836        return checkNonNullValue(key, i).intValue();
837    }
838
839    @Override
840    public int getInt(final String key, final int defaultValue) {
841        return getInteger(key, Integer.valueOf(defaultValue)).intValue();
842    }
843
844    @Override
845    public Integer getInteger(final String key, final Integer defaultValue) {
846        return convert(Integer.class, key, defaultValue, false);
847    }
848
849    /**
850     * Gets the {@code ConfigurationInterpolator} object that manages the lookup objects for resolving variables.
851     * Unless a custom interpolator has been set or the instance has been modified, the returned interpolator will
852     * resolve values from this configuration instance and support the
853     * {@link ConfigurationInterpolator#getDefaultPrefixLookups() default prefix lookups}.
854     *
855     * @return the {@code ConfigurationInterpolator} associated with this configuration
856     * @since 1.4
857     * @see ConfigurationInterpolator#getDefaultPrefixLookups()
858     */
859    @Override
860    public ConfigurationInterpolator getInterpolator() {
861        return interpolator.get();
862    }
863
864    /**
865     * {@inheritDoc} This implementation takes care of synchronization and then delegates to {@code getKeysInternal()} for
866     * obtaining the actual iterator. Note that depending on a concrete implementation, an iteration may fail if the
867     * configuration is updated concurrently.
868     */
869    @Override
870    public final Iterator<String> getKeys() {
871        return syncRead(() -> getKeysInternal(), false);
872    }
873
874    /**
875     * {@inheritDoc} This implementation returns keys that either match the prefix or start with the prefix followed by a
876     * dot ('.'). So the call {@code getKeys("db");} will find the keys {@code db}, {@code db.user}, or {@code db.password},
877     * but not the key {@code dbdriver}.
878     */
879    @Override
880    public final Iterator<String> getKeys(final String prefix) {
881        return syncRead(() -> getKeysInternal(prefix), false);
882    }
883
884    /**
885     * {@inheritDoc} This implementation returns keys that either match the prefix or start with the prefix followed by the delimiter.
886     * So the call {@code getKeys("db");} will find the keys {@code db}, {@code db@user}, or {@code db@password},
887     * but not the key {@code dbdriver}.
888     */
889    @Override
890    public final Iterator<String> getKeys(final String prefix, final String delimiter) {
891        return syncRead(() -> getKeysInternal(prefix, delimiter), false);
892    }
893
894    /**
895     * Actually creates an iterator for iterating over the keys in this configuration. This method is called by
896     * {@code getKeys()}, it has to be defined by concrete subclasses.
897     *
898     * @return an {@code Iterator} with all property keys in this configuration
899     * @since 2.0
900     */
901    protected abstract Iterator<String> getKeysInternal();
902
903    /**
904     * Gets an {@code Iterator} with all property keys starting with the specified prefix. This method is called by
905     * {@link #getKeys(String)}. It is fully implemented by delegating to {@code getKeysInternal()} and returning a special
906     * iterator which filters for the passed in prefix. Subclasses can override it if they can provide a more efficient way
907     * to iterate over specific keys only.
908     *
909     * @param prefix the prefix for the keys to be taken into account
910     * @return an {@code Iterator} returning the filtered keys
911     * @since 2.0
912     */
913    protected Iterator<String> getKeysInternal(final String prefix) {
914        return new PrefixedKeysIterator(getKeysInternal(), prefix);
915    }
916
917    /**
918     * Gets an {@code Iterator} with all property keys starting with the specified prefix and specified delimiter. This method is called by
919     * {@link #getKeys(String)}. It is fully implemented by delegating to {@code getKeysInternal()} and returning a special
920     * iterator which filters for the passed in prefix. Subclasses can override it if they can provide a more efficient way
921     * to iterate over specific keys only.
922     *
923     * @param prefix the prefix for the keys to be taken into account
924     * @param delimiter the prefix delimiter
925     * @return an {@code Iterator} returning the filtered keys
926     * @since 2.10.0
927     */
928    protected Iterator<String> getKeysInternal(final String prefix, final String delimiter) {
929        return new PrefixedKeysIterator(getKeysInternal(), prefix, delimiter);
930    }
931
932    @Override
933    public <T> List<T> getList(final Class<T> cls, final String key) {
934        return getList(cls, key, null);
935    }
936
937    /**
938     * {@inheritDoc} This implementation delegates to the generic {@code getCollection()}. As target collection a newly
939     * created {@code ArrayList} is passed in.
940     */
941    @Override
942    public <T> List<T> getList(final Class<T> cls, final String key, final List<T> defaultValue) {
943        final List<T> result = new ArrayList<>();
944        if (getCollection(cls, key, result, defaultValue) == null) {
945            return null;
946        }
947        return result;
948    }
949
950    /**
951     * {@inheritDoc}
952     *
953     * @see #getStringArray(String)
954     */
955    @Override
956    public List<Object> getList(final String key) {
957        return getList(key, new ArrayList<>());
958    }
959
960    @Override
961    public List<Object> getList(final String key, final List<?> defaultValue) {
962        final Object value = getProperty(key);
963        final List<Object> list;
964
965        if (value instanceof String) {
966            list = new ArrayList<>(1);
967            list.add(interpolate((String) value));
968        } else if (value instanceof List) {
969            list = new ArrayList<>();
970            final List<?> l = (List<?>) value;
971
972            // add the interpolated elements in the new list
973            l.forEach(elem -> list.add(interpolate(elem)));
974        } else if (value == null) {
975            // This is okay because we just return this list to the caller
976            @SuppressWarnings("unchecked")
977            final List<Object> resultList = (List<Object>) defaultValue;
978            list = resultList;
979        } else if (value.getClass().isArray()) {
980            return Arrays.asList((Object[]) value);
981        } else if (isScalarValue(value)) {
982            return Collections.singletonList((Object) value.toString());
983        } else {
984            throw new ConversionException('\'' + key + "' doesn't map to a List object: " + value + ", a " + value.getClass().getName());
985        }
986        return list;
987    }
988
989    /**
990     * Gets the {@code ListDelimiterHandler} used by this instance.
991     *
992     * @return the {@code ListDelimiterHandler}
993     * @since 2.0
994     */
995    public ListDelimiterHandler getListDelimiterHandler() {
996        return listDelimiterHandler;
997    }
998
999    /**
1000     * Gets the logger used by this configuration object.
1001     *
1002     * @return the logger
1003     * @since 2.0
1004     */
1005    public ConfigurationLogger getLogger() {
1006        return log;
1007    }
1008
1009    @Override
1010    public long getLong(final String key) {
1011        final Long l = convert(Long.class, key, null, true);
1012        return checkNonNullValue(key, l).longValue();
1013    }
1014
1015    @Override
1016    public long getLong(final String key, final long defaultValue) {
1017        return getLong(key, Long.valueOf(defaultValue)).longValue();
1018    }
1019
1020    @Override
1021    public Long getLong(final String key, final Long defaultValue) {
1022        return convert(Long.class, key, defaultValue, false);
1023    }
1024
1025    @Override
1026    public Properties getProperties(final String key) {
1027        return getProperties(key, null);
1028    }
1029
1030    /**
1031     * Gets a list of properties associated with the given configuration key.
1032     *
1033     * @param key The configuration key.
1034     * @param defaults Any default values for the returned {@code Properties} object. Ignored if {@code null}.
1035     * @return The associated properties if key is found.
1036     * @throws ConversionException is thrown if the key maps to an object that is not a String/List of Strings.
1037     * @throws IllegalArgumentException if one of the tokens is malformed (does not contain an equals sign).
1038     */
1039    public Properties getProperties(final String key, final Properties defaults) {
1040        /*
1041         * Grab an array of the tokens for this key.
1042         */
1043        final String[] tokens = getStringArray(key);
1044
1045        /*
1046         * Each token is of the form 'key=value'.
1047         */
1048        final Properties props = defaults == null ? new Properties() : new Properties(defaults);
1049        for (final String token : tokens) {
1050            final int equalSign = token.indexOf('=');
1051            if (equalSign > 0) {
1052                final String pkey = token.substring(0, equalSign).trim();
1053                final String pvalue = token.substring(equalSign + 1).trim();
1054                props.put(pkey, pvalue);
1055            } else if (tokens.length == 1 && StringUtils.isEmpty(key)) {
1056                // Semantically equivalent to an empty Properties
1057                // object.
1058                break;
1059            } else {
1060                throw new IllegalArgumentException('\'' + token + "' does not contain an equals sign");
1061            }
1062        }
1063        return props;
1064    }
1065
1066    /**
1067     * {@inheritDoc} This implementation ensures proper synchronization. Subclasses have to define the abstract
1068     * {@code getPropertyInternal()} method which is called from here.
1069     */
1070    @Override
1071    public final Object getProperty(final String key) {
1072        return syncRead(() -> getPropertyInternal(key), false);
1073    }
1074
1075    /**
1076     * Actually obtains the value of the specified property. This method is called by {@code getProperty()}. Concrete
1077     * subclasses must define it to fetch the value of the desired property.
1078     *
1079     * @param key the key of the property in question
1080     * @return the (raw) value of this property
1081     * @since 2.0
1082     */
1083    protected abstract Object getPropertyInternal(String key);
1084
1085    @Override
1086    public short getShort(final String key) {
1087        final Short s = convert(Short.class, key, null, true);
1088        return checkNonNullValue(key, s).shortValue();
1089    }
1090
1091    @Override
1092    public short getShort(final String key, final short defaultValue) {
1093        return getShort(key, Short.valueOf(defaultValue)).shortValue();
1094    }
1095
1096    @Override
1097    public Short getShort(final String key, final Short defaultValue) {
1098        return convert(Short.class, key, defaultValue, false);
1099    }
1100
1101    /**
1102     * {@inheritDoc}
1103     *
1104     * @see #setThrowExceptionOnMissing(boolean)
1105     */
1106    @Override
1107    public String getString(final String key) {
1108        return convert(String.class, key, null, true);
1109    }
1110
1111    @Override
1112    public String getString(final String key, final String defaultValue) {
1113        final String result = convert(String.class, key, null, false);
1114        return result != null ? result : interpolate(defaultValue);
1115    }
1116
1117    /**
1118     * Gets an array of strings associated with the given configuration key. If the key doesn't map to an existing object, an
1119     * empty array is returned. When a property is added to a configuration, it is checked whether it contains multiple
1120     * values. This is obvious if the added object is a list or an array. For strings the association
1121     * {@link ListDelimiterHandler} is consulted to find out whether the string can be split into multiple values.
1122     *
1123     * @param key The configuration key.
1124     * @return The associated string array if key is found.
1125     * @throws ConversionException is thrown if the key maps to an object that is not a String/List of Strings.
1126     * @see #setListDelimiterHandler(ListDelimiterHandler)
1127     */
1128    @Override
1129    public String[] getStringArray(final String key) {
1130        final String[] result = (String[]) getArray(String.class, key);
1131        return result == null ? ArrayUtils.EMPTY_STRING_ARRAY : result;
1132    }
1133
1134    /**
1135     * Gets the object responsible for synchronizing this configuration. All access to this configuration - both read and
1136     * write access - is controlled by this object. This implementation never returns <strong>null</strong>. If no
1137     * {@code Synchronizer} has been set, a {@link NoOpSynchronizer} is returned. So, per default, instances of
1138     * {@code AbstractConfiguration} are not thread-safe unless a suitable {@code Synchronizer} is set!
1139     *
1140     * @return the {@code Synchronizer} used by this instance
1141     * @since 2.0
1142     */
1143    @Override
1144    public final Synchronizer getSynchronizer() {
1145        return synchronizer;
1146    }
1147
1148    @Override
1149    public ImmutableConfiguration immutableSubset(final String prefix) {
1150        return ConfigurationUtils.unmodifiableConfiguration(subset(prefix));
1151    }
1152
1153    /**
1154     * Initializes the logger. Supports <strong>null</strong> input. This method can be called by derived classes in order to enable
1155     * logging.
1156     *
1157     * @param log the logger
1158     * @since 2.0
1159     */
1160    protected final void initLogger(final ConfigurationLogger log) {
1161        this.log = log != null ? log : ConfigurationLogger.newDummyLogger();
1162    }
1163
1164    /**
1165     * Creates a default {@code ConfigurationInterpolator} which is initialized with all default {@code Lookup} objects.
1166     * This method is called by the constructor. It ensures that default interpolation works for every new configuration
1167     * instance.
1168     */
1169    private void installDefaultInterpolator() {
1170        installInterpolator(ConfigurationInterpolator.getDefaultPrefixLookups(), null);
1171    }
1172
1173    /**
1174     * {@inheritDoc} This implementation creates a new {@code ConfigurationInterpolator} instance and initializes it with
1175     * the given {@code Lookup} objects. In addition, it adds a specialized default {@code Lookup} object which queries this
1176     * {@code Configuration}.
1177     *
1178     * @since 2.0
1179     */
1180    @Override
1181    public final void installInterpolator(final Map<String, ? extends Lookup> prefixLookups, final Collection<? extends Lookup> defLookups) {
1182        final InterpolatorSpecification spec = new InterpolatorSpecification.Builder().withPrefixLookups(prefixLookups).withDefaultLookups(defLookups)
1183            .withDefaultLookup(new ConfigurationLookup(this)).create();
1184        setInterpolator(ConfigurationInterpolator.fromSpecification(spec));
1185    }
1186
1187    /**
1188     * Returns the interpolated value. This implementation delegates to the current {@code ConfigurationInterpolator}. If no
1189     * {@code ConfigurationInterpolator} is set, the passed in value is returned without changes.
1190     *
1191     * @param value the value to interpolate
1192     * @return the value with variables substituted
1193     */
1194    protected Object interpolate(final Object value) {
1195        final ConfigurationInterpolator ci = getInterpolator();
1196        return ci != null ? ci.interpolate(value) : value;
1197    }
1198
1199    /**
1200     * interpolate key names to handle ${key} stuff
1201     *
1202     * @param base string to interpolate
1203     * @return the key name with the ${key} substituted
1204     */
1205    protected String interpolate(final String base) {
1206        return Objects.toString(interpolate((Object) base), null);
1207    }
1208
1209    /**
1210     * Returns a configuration with the same content as this configuration, but with all variables replaced by their actual
1211     * values. This method tries to clone the configuration and then perform interpolation on all properties. So property
1212     * values of the form {@code ${var}} will be resolved as far as possible (if a variable cannot be resolved, it remains
1213     * unchanged). This operation is useful if the content of a configuration is to be exported or processed by an external
1214     * component that does not support variable interpolation.
1215     *
1216     * @return a configuration with all variables interpolated
1217     * @throws org.apache.commons.configuration2.ex.ConfigurationRuntimeException if this configuration cannot be cloned
1218     * @since 1.5
1219     */
1220    public Configuration interpolatedConfiguration() {
1221        // first clone this configuration
1222        final AbstractConfiguration c = (AbstractConfiguration) ConfigurationUtils.cloneConfiguration(this);
1223
1224        // now perform interpolation
1225        c.setListDelimiterHandler(new DisabledListDelimiterHandler());
1226        getKeys().forEachRemaining(key -> c.setProperty(key, getList(key)));
1227        c.setListDelimiterHandler(getListDelimiterHandler());
1228        return c;
1229    }
1230
1231    /**
1232     * {@inheritDoc} This implementation handles synchronization and delegates to {@code isEmptyInternal()}.
1233     */
1234    @Override
1235    public final boolean isEmpty() {
1236        return syncRead(() -> isEmptyInternal(), false);
1237    }
1238
1239    /**
1240     * Actually checks whether this configuration contains data. This method is called by {@code isEmpty()}. It has to be
1241     * defined by concrete subclasses.
1242     *
1243     * @return <strong>true</strong> if this configuration contains no data, <strong>false</strong> otherwise
1244     * @since 2.0
1245     */
1246    protected abstract boolean isEmptyInternal();
1247
1248    /**
1249     * Checks whether the specified object is a scalar value. This method is called by {@code getList()} and
1250     * {@code getStringArray()} if the property requested is not a string, a list, or an array. If it returns <strong>true</strong>,
1251     * the calling method transforms the value to a string and returns a list or an array with this single element. This
1252     * implementation returns <strong>true</strong> if the value is of a wrapper type for a primitive type.
1253     *
1254     * @param value the value to be checked
1255     * @return a flag whether the value is a scalar
1256     * @since 1.7
1257     */
1258    protected boolean isScalarValue(final Object value) {
1259        return ClassUtils.wrapperToPrimitive(value.getClass()) != null;
1260    }
1261
1262    /**
1263     * Returns true if missing values throw Exceptions.
1264     *
1265     * @return true if missing values throw Exceptions
1266     */
1267    public boolean isThrowExceptionOnMissing() {
1268        return throwExceptionOnMissing;
1269    }
1270
1271    /**
1272     * {@inheritDoc} This implementation delegates to {@code beginRead()} or {@code beginWrite()}, depending on the
1273     * {@code LockMode} argument. Subclasses can override these protected methods to perform additional steps when a
1274     * configuration is locked.
1275     *
1276     * @throws NullPointerException if the argument is <strong>null</strong>
1277     * @since 2.0
1278     */
1279    @Override
1280    public final void lock(final LockMode mode) {
1281        switch (mode) {
1282        case READ:
1283            beginRead(false);
1284            break;
1285        case WRITE:
1286            beginWrite(false);
1287            break;
1288        default:
1289            throw new IllegalArgumentException("Unsupported LockMode: " + mode);
1290        }
1291    }
1292
1293    /**
1294     * Sets the {@code ConfigurationDecoder} for this configuration. This object is used by
1295     * {@link #getEncodedString(String)}.
1296     *
1297     * @param configurationDecoder the {@code ConfigurationDecoder}
1298     * @since 2.0
1299     */
1300    public void setConfigurationDecoder(final ConfigurationDecoder configurationDecoder) {
1301        this.configurationDecoder = configurationDecoder;
1302    }
1303
1304    /**
1305     * Sets the {@code ConversionHandler} to be used by this instance. The {@code ConversionHandler} is responsible for
1306     * every kind of data type conversion. It is consulted by all get methods returning results in specific data types. A
1307     * newly created configuration uses a default {@code ConversionHandler} implementation. This can be changed while
1308     * initializing the configuration (for example via a builder). Note that access to this property is not synchronized.
1309     *
1310     * @param conversionHandler the {@code ConversionHandler} to be used (must not be <strong>null</strong>)
1311     * @throws IllegalArgumentException if the {@code ConversionHandler} is <strong>null</strong>
1312     * @since 2.0
1313     */
1314    public void setConversionHandler(final ConversionHandler conversionHandler) {
1315        if (conversionHandler == null) {
1316            throw new IllegalArgumentException("ConversionHandler must not be null!");
1317        }
1318        this.conversionHandler = conversionHandler;
1319    }
1320
1321    /**
1322     * Adds all {@code Lookup} objects in the given collection as default lookups (i.e. lookups without a variable prefix)
1323     * to the {@code ConfigurationInterpolator} object of this configuration. In addition, it adds a specialized default
1324     * {@code Lookup} object which queries this {@code Configuration}. The set of {@code Lookup} objects with prefixes is
1325     * not modified by this method. If this configuration does not have a {@code ConfigurationInterpolator}, a new instance
1326     * is created. Note: This method is mainly intended to be used for initializing a configuration when it is created by a
1327     * builder. Normal client code should better call {@link #installInterpolator(Map, Collection)} to define the
1328     * {@code ConfigurationInterpolator} in a single step.
1329     *
1330     * @param lookups the collection with default {@code Lookup} objects to be added
1331     * @since 2.0
1332     */
1333    public void setDefaultLookups(final Collection<? extends Lookup> lookups) {
1334        boolean success;
1335        do {
1336            final ConfigurationInterpolator ciOld = getInterpolator();
1337            final ConfigurationInterpolator ciNew = ciOld != null ? ciOld : new ConfigurationInterpolator();
1338            Lookup confLookup = findConfigurationLookup(ciNew);
1339            if (confLookup == null) {
1340                confLookup = new ConfigurationLookup(this);
1341            } else {
1342                ciNew.removeDefaultLookup(confLookup);
1343            }
1344            ciNew.addDefaultLookups(lookups);
1345            ciNew.addDefaultLookup(confLookup);
1346            success = interpolator.compareAndSet(ciOld, ciNew);
1347        } while (!success);
1348    }
1349
1350    /**
1351     * {@inheritDoc} This implementation sets the passed in object without further modifications. A <strong>null</strong> argument is
1352     * allowed; this disables interpolation.
1353     *
1354     * @since 2.0
1355     */
1356    @Override
1357    public final void setInterpolator(final ConfigurationInterpolator ci) {
1358        interpolator.set(ci);
1359    }
1360
1361    /**
1362     * <p>
1363     * Sets the {@code ListDelimiterHandler} to be used by this instance. This object is invoked every time when dealing
1364     * with string properties that may contain a list delimiter and thus have to be split to multiple values. Per default, a
1365     * {@code ListDelimiterHandler} implementation is set which does not support list splitting. This can be changed for
1366     * instance by setting a {@link org.apache.commons.configuration2.convert.DefaultListDelimiterHandler
1367     * DefaultListDelimiterHandler} object.
1368     * </p>
1369     * <p>
1370     * <strong>Warning:</strong> Be careful when changing the list delimiter handler when the configuration has already been
1371     * loaded/populated. List handling is typically applied already when properties are added to the configuration. If later
1372     * another handler is set which processes lists differently, results may be unexpected; some operations may even cause
1373     * exceptions.
1374     * </p>
1375     *
1376     * @param listDelimiterHandler the {@code ListDelimiterHandler} to be used (must not be <strong>null</strong>)
1377     * @throws IllegalArgumentException if the {@code ListDelimiterHandler} is <strong>null</strong>
1378     * @since 2.0
1379     */
1380    public void setListDelimiterHandler(final ListDelimiterHandler listDelimiterHandler) {
1381        if (listDelimiterHandler == null) {
1382            throw new IllegalArgumentException("List delimiter handler must not be null!");
1383        }
1384        this.listDelimiterHandler = listDelimiterHandler;
1385    }
1386
1387    /**
1388     * Allows setting the logger to be used by this configuration object. This method makes it possible for clients to
1389     * exactly control logging behavior. Per default a logger is set that will ignore all log messages. Derived classes that
1390     * want to enable logging should call this method during their initialization with the logger to be used. It is legal to
1391     * pass a <strong>null</strong> logger; in this case, logging will be disabled.
1392     *
1393     * @param log the new logger
1394     * @since 2.0
1395     */
1396    public void setLogger(final ConfigurationLogger log) {
1397        initLogger(log);
1398    }
1399
1400    /**
1401     * Sets the specified {@code ConfigurationInterpolator} as the parent of this configuration's
1402     * {@code ConfigurationInterpolator}. If this configuration does not have a {@code ConfigurationInterpolator}, a new
1403     * instance is created. Note: This method is mainly intended to be used for initializing a configuration when it is
1404     * created by a builder. Normal client code can directly update the {@code ConfigurationInterpolator}.
1405     *
1406     * @param parent the parent {@code ConfigurationInterpolator} to be set
1407     * @since 2.0
1408     */
1409    public void setParentInterpolator(final ConfigurationInterpolator parent) {
1410        boolean success;
1411        do {
1412            final ConfigurationInterpolator ciOld = getInterpolator();
1413            final ConfigurationInterpolator ciNew = ciOld != null ? ciOld : new ConfigurationInterpolator();
1414            ciNew.setParentInterpolator(parent);
1415            success = interpolator.compareAndSet(ciOld, ciNew);
1416        } while (!success);
1417    }
1418
1419    /**
1420     * Registers all {@code Lookup} objects in the given map at the current {@code ConfigurationInterpolator} of this
1421     * configuration. The set of default lookup objects (for variables without a prefix) is not modified by this method. If
1422     * this configuration does not have a {@code ConfigurationInterpolator}, a new instance is created. Note: This method is
1423     * mainly intended to be used for initializing a configuration when it is created by a builder. Normal client code
1424     * should better call {@link #installInterpolator(Map, Collection)} to define the {@code ConfigurationInterpolator} in a
1425     * single step.
1426     *
1427     * @param lookups a map with new {@code Lookup} objects and their prefixes (may be <strong>null</strong>)
1428     * @since 2.0
1429     */
1430    public void setPrefixLookups(final Map<String, ? extends Lookup> lookups) {
1431        boolean success;
1432        do {
1433            // do this in a loop because the ConfigurationInterpolator
1434            // instance may be changed by another thread
1435            final ConfigurationInterpolator ciOld = getInterpolator();
1436            final ConfigurationInterpolator ciNew = ciOld != null ? ciOld : new ConfigurationInterpolator();
1437            ciNew.registerLookups(lookups);
1438            success = interpolator.compareAndSet(ciOld, ciNew);
1439        } while (!success);
1440    }
1441
1442    @Override
1443    public final void setProperty(final String key, final Object value) {
1444        syncWrite(() -> {
1445            fireEvent(ConfigurationEvent.SET_PROPERTY, key, value, true);
1446            setPropertyInternal(key, value);
1447            fireEvent(ConfigurationEvent.SET_PROPERTY, key, value, false);
1448        }, false);
1449    }
1450
1451    /**
1452     * Actually sets the value of a property. This method is called by {@code setProperty()}. It provides a default
1453     * implementation of this functionality by clearing the specified key and delegating to {@code addProperty()}.
1454     * Subclasses should override this method if they can provide a more efficient algorithm for setting a property value.
1455     *
1456     * @param key the property key
1457     * @param value the new property value
1458     * @since 2.0
1459     */
1460    protected void setPropertyInternal(final String key, final Object value) {
1461        setDetailEvents(false);
1462        try {
1463            clearProperty(key);
1464            addProperty(key, value);
1465        } finally {
1466            setDetailEvents(true);
1467        }
1468    }
1469
1470    /**
1471     * Sets the object responsible for synchronizing this configuration. This method has to be called with a suitable
1472     * {@code Synchronizer} object when initializing this configuration instance in order to make it thread-safe.
1473     *
1474     * @param synchronizer the new {@code Synchronizer}; can be <strong>null</strong>, then this instance uses a
1475     *        {@link NoOpSynchronizer}
1476     * @since 2.0
1477     */
1478    @Override
1479    public final void setSynchronizer(final Synchronizer synchronizer) {
1480        this.synchronizer = synchronizer != null ? synchronizer : NoOpSynchronizer.INSTANCE;
1481    }
1482
1483    /**
1484     * Allows to set the {@code throwExceptionOnMissing} flag. This flag controls the behavior of property getter methods
1485     * that return objects if the requested property is missing. If the flag is set to <strong>false</strong> (which is the default
1486     * value), these methods will return <strong>null</strong>. If set to <strong>true</strong>, they will throw a
1487     * {@code NoSuchElementException} exception. Note that getter methods for primitive data types are not affected by this
1488     * flag.
1489     *
1490     * @param throwExceptionOnMissing The new value for the property
1491     */
1492    public void setThrowExceptionOnMissing(final boolean throwExceptionOnMissing) {
1493        this.throwExceptionOnMissing = throwExceptionOnMissing;
1494    }
1495
1496    /**
1497     * {@inheritDoc} This implementation handles synchronization and delegates to {@code sizeInternal()}.
1498     */
1499    @Override
1500    public final int size() {
1501        return syncRead(this::sizeInternal, false);
1502    }
1503
1504    /**
1505     * Actually calculates the size of this configuration. This method is called by {@code size()} with a read lock held.
1506     * The base implementation provided here calculates the size based on the iterator returned by {@code getKeys()}. Sub
1507     * classes which can determine the size in a more efficient way should override this method.
1508     *
1509     * @return the size of this configuration (i.e. the number of keys)
1510     */
1511    protected int sizeInternal() {
1512        int size = 0;
1513        for (final Iterator<String> keyIt = getKeysInternal(); keyIt.hasNext(); size++) {
1514            keyIt.next();
1515        }
1516        return size;
1517    }
1518
1519    @Override
1520    public Configuration subset(final String prefix) {
1521        return new SubsetConfiguration(this, prefix, DELIMITER);
1522    }
1523
1524    <T, E extends Throwable> T syncRead(final FailableSupplier<T, E> supplier, final boolean optimize) throws E {
1525        beginRead(optimize);
1526        try {
1527            return supplier.get();
1528        } finally {
1529            endRead();
1530        }
1531    }
1532
1533    void syncRead(final Runnable runnable, final boolean optimize) {
1534        beginRead(optimize);
1535        try {
1536            runnable.run();
1537        } finally {
1538            endRead();
1539        }
1540    }
1541
1542    <T> T syncReadValue(final T value, final boolean optimize) {
1543        beginRead(optimize);
1544        try {
1545            return value;
1546        } finally {
1547            endRead();
1548        }
1549    }
1550
1551    <E extends Throwable> void syncWrite(final FailableRunnable<E> runnable, final boolean optimize) throws E {
1552        beginWrite(optimize);
1553        try {
1554            runnable.run();
1555        } finally {
1556            endWrite();
1557        }
1558    }
1559
1560    /**
1561     * {@inheritDoc} This implementation delegates to {@code endRead()} or {@code endWrite()}, depending on the
1562     * {@code LockMode} argument. Subclasses can override these protected methods to perform additional steps when a
1563     * configuration's lock is released.
1564     *
1565     * @throws NullPointerException if the argument is <strong>null</strong>
1566     */
1567    @Override
1568    public final void unlock(final LockMode mode) {
1569        switch (mode) {
1570        case READ:
1571            endRead();
1572            break;
1573        case WRITE:
1574            endWrite();
1575            break;
1576        default:
1577            throw new IllegalArgumentException("Unsupported LockMode: " + mode);
1578        }
1579    }
1580}