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