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