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