    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}