org.apache.commons.configuration2.AbstractConfiguration Maven / Gradle / Ivy
Show all versions of commons-configuration2 Show documentation
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.commons.configuration2;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Properties;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
import org.apache.commons.configuration2.convert.ConversionHandler;
import org.apache.commons.configuration2.convert.DefaultConversionHandler;
import org.apache.commons.configuration2.convert.DisabledListDelimiterHandler;
import org.apache.commons.configuration2.convert.ListDelimiterHandler;
import org.apache.commons.configuration2.event.BaseEventSource;
import org.apache.commons.configuration2.event.ConfigurationErrorEvent;
import org.apache.commons.configuration2.event.ConfigurationEvent;
import org.apache.commons.configuration2.event.EventListener;
import org.apache.commons.configuration2.ex.ConversionException;
import org.apache.commons.configuration2.interpol.ConfigurationInterpolator;
import org.apache.commons.configuration2.interpol.InterpolatorSpecification;
import org.apache.commons.configuration2.interpol.Lookup;
import org.apache.commons.configuration2.io.ConfigurationLogger;
import org.apache.commons.configuration2.sync.LockMode;
import org.apache.commons.configuration2.sync.NoOpSynchronizer;
import org.apache.commons.configuration2.sync.Synchronizer;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.ClassUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
/**
*
* Abstract configuration class. Provides basic functionality but does not store any data.
*
*
* If you want to write your own Configuration class then you should implement only abstract methods from this class. A
* lot of functionality needed by typical implementations of the {@code Configuration} interface is already provided by
* this base class. Following is a list of features implemented here:
*
*
* - Data conversion support. The various data types required by the {@code Configuration} interface are already
* handled by this base class. A concrete sub class only needs to provide a generic {@code getProperty()} method.
* - Support for variable interpolation. Property values containing special variable tokens (like {@code ${var}}) will
* be replaced by their corresponding values.
* - Optional support for string lists. The values of properties to be added to this configuration are checked whether
* they contain a list delimiter character. If this is the case and if list splitting is enabled, the string is split
* and multiple values are added for this property. List splitting is controlled by a {@link ListDelimiterHandler}
* object which can be set using the {@link #setListDelimiterHandler(ListDelimiterHandler)} method. It is disabled per
* default. To enable this feature, set a suitable {@code ListDelimiterHandler}, e.g. an instance of
* {@link org.apache.commons.configuration2.convert.DefaultListDelimiterHandler DefaultListDelimiterHandler} configured
* with the desired list delimiter character.
* - Allows specifying how missing properties are treated. Per default the get methods returning an object will return
* null if the searched property key is not found (and no default value is provided). With the
* {@code setThrowExceptionOnMissing()} method this behavior can be changed to throw an exception when a requested
* property cannot be found.
* - Basic event support. Whenever this configuration is modified registered event listeners are notified. Refer to
* the various {@code EVENT_XXX} constants to get an impression about which event types are supported.
* - Support for proper synchronization based on the {@link Synchronizer} interface.
*
*
* Most methods defined by the {@code Configuration} interface are already implemented in this class. Many method
* implementations perform basic book-keeping tasks (e.g. firing events, handling synchronization), and then delegate to
* other (protected) methods executing the actual work. Subclasses override these protected methods to define or adapt
* behavior. The public entry point methods are final to prevent subclasses from breaking basic functionality.
*
*/
public abstract class AbstractConfiguration extends BaseEventSource implements Configuration {
/**
* Checks an object provided as default value for the {@code getArray()} method. Throws an exception if this is not an
* array with the correct component type.
*
* @param cls the component class for the array
* @param defaultValue the default value object to be checked
* @throws IllegalArgumentException if this is not a valid default object
*/
private static void checkDefaultValueArray(final Class> cls, final Object defaultValue) {
if (defaultValue != null && (!defaultValue.getClass().isArray() || !cls.isAssignableFrom(defaultValue.getClass().getComponentType()))) {
throw new IllegalArgumentException(
"The type of the default value (" + defaultValue.getClass() + ")" + " is not an array of the specified class (" + cls + ")");
}
}
/**
* Checks whether the specified value is null and throws an exception in this case. This method is used by
* conversion methods returning primitive Java types. Here values to be returned must not be null.
*
* @param the type of the object to be checked
* @param key the key which caused the problem
* @param value the value to be checked
* @return the passed in value for chaining this method call
* @throws NoSuchElementException if the value is null
*/
private static T checkNonNullValue(final String key, final T value) {
if (value == null) {
throwMissingPropertyException(key);
}
return value;
}
/**
* Finds a {@code ConfigurationLookup} pointing to the specified configuration in the default lookups for the specified
* {@code ConfigurationInterpolator}.
*
* @param ci the {@code ConfigurationInterpolator} in question
* @param targetConf the target configuration of the searched lookup
* @return the found {@code Lookup} object or null
*/
private static Lookup findConfigurationLookup(final ConfigurationInterpolator ci, final ImmutableConfiguration targetConf) {
for (final Lookup l : ci.getDefaultLookups()) {
if (l instanceof ConfigurationLookup && targetConf == ((ConfigurationLookup) l).getConfiguration()) {
return l;
}
}
return null;
}
/**
* Handles the default collection for a collection conversion. This method fills the target collection with the content
* of the default collection. Both collections may be null.
*
* @param target the target collection
* @param defaultValue the default collection
* @return the initialized target collection
*/
private static Collection handleDefaultCollection(final Collection target, final Collection defaultValue) {
if (defaultValue == null) {
return null;
}
final Collection result;
if (target == null) {
result = new ArrayList<>(defaultValue);
} else {
target.addAll(defaultValue);
result = target;
}
return result;
}
/**
* Helper method for throwing an exception for a key that does not map to an existing object.
*
* @param key the key (to be part of the error message)
*/
private static void throwMissingPropertyException(final String key) {
throw new NoSuchElementException(String.format("Key '%s' does not map to an existing object!", key));
}
/** The list delimiter handler. */
private ListDelimiterHandler listDelimiterHandler;
/** The conversion handler. */
private ConversionHandler conversionHandler;
/**
* Whether the configuration should throw NoSuchElementExceptions or simply return null when a property does not exist.
* Defaults to return null.
*/
private boolean throwExceptionOnMissing;
/** Stores a reference to the object that handles variable interpolation. */
private AtomicReference interpolator;
/** The object responsible for synchronization. */
private volatile Synchronizer synchronizer;
/** The object used for dealing with encoded property values. */
private ConfigurationDecoder configurationDecoder;
/** Stores the logger. */
private ConfigurationLogger log;
/**
* Creates a new instance of {@code AbstractConfiguration}.
*/
public AbstractConfiguration() {
interpolator = new AtomicReference<>();
initLogger(null);
installDefaultInterpolator();
listDelimiterHandler = DisabledListDelimiterHandler.INSTANCE;
conversionHandler = DefaultConversionHandler.INSTANCE;
}
/**
* Adds a special {@link EventListener} object to this configuration that will log all internal errors. This method is
* intended to be used by certain derived classes, for which it is known that they can fail on property access (e.g.
* {@code DatabaseConfiguration}).
*
* @since 1.4
*/
public final void addErrorLogListener() {
addEventListener(ConfigurationErrorEvent.ANY, event -> getLogger().warn("Internal error", event.getCause()));
}
@Override
public final void addProperty(final String key, final Object value) {
beginWrite(false);
try {
fireEvent(ConfigurationEvent.ADD_PROPERTY, key, value, true);
addPropertyInternal(key, value);
fireEvent(ConfigurationEvent.ADD_PROPERTY, key, value, false);
} finally {
endWrite();
}
}
/**
* Adds a key/value pair to the Configuration. Override this method to provide write access to underlying Configuration
* store.
*
* @param key key to use for mapping
* @param value object to store
*/
protected abstract void addPropertyDirect(String key, Object value);
/**
* Actually adds a property to this configuration. This method is called by {@code addProperty()}. It performs list
* splitting if necessary and delegates to {@link #addPropertyDirect(String, Object)} for every single property value.
*
* @param key the key of the property to be added
* @param value the new property value
* @since 2.0
*/
protected void addPropertyInternal(final String key, final Object value) {
getListDelimiterHandler().parse(value).forEach(obj -> addPropertyDirect(key, obj));
}
/**
* Appends the content of the specified configuration to this configuration. The values of all properties contained in
* the specified configuration will be appended to this configuration. So if a property is already present in this
* configuration, its new value will be a union of the values in both configurations. Note: This method won't
* work well when appending hierarchical configurations because it is not able to copy information about the properties'
* structure (i.e. the parent-child-relationships will get lost). So when dealing with hierarchical configuration
* objects their {@link BaseHierarchicalConfiguration#clone() clone()} methods should be used.
*
* @param c the configuration to be appended (can be null, then this operation will have no effect)
* @since 1.5
*/
public void append(final Configuration c) {
if (c != null) {
c.lock(LockMode.READ);
try {
c.getKeys().forEachRemaining(key -> addProperty(key, encodeForCopy(c.getProperty(key))));
} finally {
c.unlock(LockMode.READ);
}
}
}
/**
* Notifies this configuration's {@link Synchronizer} that a read operation is about to start. This method is called by
* all methods which access this configuration in a read-only mode. Subclasses may override it to perform additional
* actions before this read operation. The boolean optimize argument can be evaluated by overridden methods in
* derived classes. Some operations which require a lock do not need a fully initialized configuration object. By
* setting this flag to true, such operations can give a corresponding hint. An overridden
* implementation of {@code beginRead()} can then decide to skip some initialization steps. All basic operations in this
* class (and most of the basic {@code Configuration} implementations) call this method with a parameter value of
* false. In any case the inherited method must be called! Otherwise, proper synchronization is
* not guaranteed.
*
* @param optimize a flag whether optimization can be performed
* @since 2.0
*/
protected void beginRead(final boolean optimize) {
getSynchronizer().beginRead();
}
/**
* Notifies this configuration's {@link Synchronizer} that an update operation is about to start. This method is called
* by all methods which modify this configuration. Subclasses may override it to perform additional operations before an
* update. For a description of the boolean optimize argument refer to the documentation of
* {@code beginRead()}. In any case the inherited method must be called! Otherwise, proper synchronization is
* not guaranteed.
*
* @param optimize a flag whether optimization can be performed
* @see #beginRead(boolean)
* @since 2.0
*/
protected void beginWrite(final boolean optimize) {
getSynchronizer().beginWrite();
}
@Override
public final void clear() {
beginWrite(false);
try {
fireEvent(ConfigurationEvent.CLEAR, null, null, true);
clearInternal();
fireEvent(ConfigurationEvent.CLEAR, null, null, false);
} finally {
endWrite();
}
}
/**
* Clears the whole configuration. This method is called by {@code clear()} after some preparations have been made. This
* base implementation uses the iterator provided by {@code getKeys()} to remove every single property. Subclasses
* should override this method if there is a more efficient way of clearing the configuration.
*/
protected void clearInternal() {
setDetailEvents(false);
boolean useIterator = true;
try {
final Iterator it = getKeys();
while (it.hasNext()) {
final String key = it.next();
if (useIterator) {
try {
it.remove();
} catch (final UnsupportedOperationException usoex) {
useIterator = false;
}
}
if (useIterator && containsKey(key)) {
useIterator = false;
}
if (!useIterator) {
// workaround for Iterators that do not remove the
// property
// on calling remove() or do not support remove() at all
clearProperty(key);
}
}
} finally {
setDetailEvents(true);
}
}
/**
* Removes the specified property from this configuration. This implementation performs some preparations and then
* delegates to {@code clearPropertyDirect()}, which will do the real work.
*
* @param key the key to be removed
*/
@Override
public final void clearProperty(final String key) {
beginWrite(false);
try {
fireEvent(ConfigurationEvent.CLEAR_PROPERTY, key, null, true);
clearPropertyDirect(key);
fireEvent(ConfigurationEvent.CLEAR_PROPERTY, key, null, false);
} finally {
endWrite();
}
}
/**
* Removes the specified property from this configuration. This method is called by {@code clearProperty()} after it has
* done some preparations. It must be overridden in sub classes.
*
* @param key the key to be removed
*/
protected abstract void clearPropertyDirect(String key);
/**
* Creates a clone of the {@code ConfigurationInterpolator} used by this instance. This method can be called by
* {@code clone()} implementations of derived classes. Normally, the {@code ConfigurationInterpolator} of a
* configuration instance must not be shared with other instances because it contains a specific {@code Lookup} object
* pointing to the owning configuration. This has to be taken into account when cloning a configuration. This method
* creates a new {@code ConfigurationInterpolator} for this configuration instance which contains all lookup objects
* from the original {@code ConfigurationInterpolator} except for the configuration specific lookup pointing to the
* passed in original configuration. This one is replaced by a corresponding {@code Lookup} referring to this
* configuration.
*
* @param orgConfig the original configuration from which this one was cloned
* @since 2.0
*/
protected void cloneInterpolator(final AbstractConfiguration orgConfig) {
interpolator = new AtomicReference<>();
final ConfigurationInterpolator orgInterpolator = orgConfig.getInterpolator();
final List defaultLookups = orgInterpolator.getDefaultLookups();
final Lookup lookup = findConfigurationLookup(orgInterpolator, orgConfig);
if (lookup != null) {
defaultLookups.remove(lookup);
}
installInterpolator(orgInterpolator.getLookups(), defaultLookups);
}
/**
* Checks if the specified value exists in the properties structure mapped by the provided keys.
*
* @param keys an Iterator of String keys to search for the value
* @param value the String value to search for in the properties
* @return true if the value is found in the properties, false otherwise
* @since 2.11.0
*/
protected boolean contains(final Iterator keys, final Object value) {
while (keys.hasNext()) {
if (Objects.equals(value, getProperty(keys.next()))) {
return true;
}
}
return false;
}
/**
* {@inheritDoc} This implementation handles synchronization and delegates to {@code containsKeyInternal()}.
*/
@Override
public final boolean containsKey(final String key) {
beginRead(false);
try {
return containsKeyInternal(key);
} finally {
endRead();
}
}
/**
* Actually checks whether the specified key is contained in this configuration. This method is called by
* {@code containsKey()}. It has to be defined by concrete subclasses.
*
* @param key the key in question
* @return true if this key is contained in this configuration, false otherwise
* @since 2.0
*/
protected abstract boolean containsKeyInternal(String key);
/**
* {@inheritDoc} This implementation handles synchronization and delegates to {@code containsKeyInternal()}.
* @since 2.11.0
*/
@Override
public final boolean containsValue(final Object value) {
beginRead(false);
try {
return containsValueInternal(value);
} finally {
endRead();
}
}
/**
* 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
* {@link #containsKeyInternal containsKey} method.
*
* The implementation of this method will be different depending on the type of Configuration used.
*
*
*
* Note that this method is identical in functionality to {@link #containsValue containsValue}, (which is part of the {@link ImmutableConfiguration}
* interface).
*
*
* @param value the value in question
* @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;
* {@code false} otherwise.
* @since 2.11.0
*/
protected abstract boolean containsValueInternal(Object value);
/**
* Helper method for obtaining a property value with a type conversion.
*
* @param the target type of the conversion
* @param cls the target class
* @param key the key of the desired property
* @param defValue a default value
* @param throwOnMissing a flag whether an exception should be thrown for a missing value
* @return the converted value
*/
private T convert(final Class cls, final String key, final T defValue, final boolean throwOnMissing) {
if (cls.isArray()) {
return cls.cast(convertToArray(cls.getComponentType(), key, defValue));
}
final T result = getAndConvertProperty(cls, key, defValue);
if (result == null) {
if (throwOnMissing && isThrowExceptionOnMissing()) {
throwMissingPropertyException(key);
}
return defValue;
}
return result;
}
/**
* Performs a conversion to an array result class. This implementation delegates to the {@link ConversionHandler} to
* perform the actual type conversion. If this results in a null result (because the property is undefined), the
* default value is returned. It is checked whether the default value is an array with the correct component type. If
* not, an exception is thrown.
*
* @param cls the component class of the array
* @param key the configuration key
* @param defaultValue an optional default value
* @return the converted array
* @throws IllegalArgumentException if the default value is not a compatible array
*/
private Object convertToArray(final Class> cls, final String key, final Object defaultValue) {
checkDefaultValueArray(cls, defaultValue);
return ObjectUtils.defaultIfNull(getConversionHandler().toArray(getProperty(key), cls, getInterpolator()), defaultValue);
}
/**
* Copies the content of the specified configuration into this configuration. If the specified configuration contains a
* key that is also present in this configuration, the value of this key will be replaced by the new value.
* Note: This method won't work well when copying hierarchical configurations because it is not able to copy
* information about the properties' structure (i.e. the parent-child-relationships will get lost). So when dealing with
* hierarchical configuration objects their {@link BaseHierarchicalConfiguration#clone() clone()} methods should be
* used.
*
* @param c the configuration to copy (can be null, then this operation will have no effect)
* @since 1.5
*/
public void copy(final Configuration c) {
if (c != null) {
c.lock(LockMode.READ);
try {
c.getKeys().forEachRemaining(key -> setProperty(key, encodeForCopy(c.getProperty(key))));
} finally {
c.unlock(LockMode.READ);
}
}
}
/**
* Encodes a property value so that it can be added to this configuration. This method deals with list delimiters. The
* passed in object has to be escaped so that an add operation yields the same result. If it is a list, all of its
* values have to be escaped.
*
* @param value the value to be encoded
* @return the encoded value
*/
private Object encodeForCopy(final Object value) {
if (value instanceof Collection) {
return encodeListForCopy((Collection>) value);
}
return getListDelimiterHandler().escape(value, ListDelimiterHandler.NOOP_TRANSFORMER);
}
/**
* Encodes a list with property values so that it can be added to this configuration. This method calls
* {@code encodeForCopy()} for all list elements.
*
* @param values the list to be encoded
* @return a list with encoded elements
*/
private Object encodeListForCopy(final Collection> values) {
return values.stream().map(this::encodeForCopy).collect(Collectors.toList());
}
/**
* Notifies this configuration's {@link Synchronizer} that a read operation has finished. This method is called by all
* methods which access this configuration in a read-only manner at the end of their execution. Subclasses may override
* it to perform additional actions after this read operation. In any case the inherited method must be called!
* Otherwise, the read lock will not be released.
*
* @since 2.0
*/
protected void endRead() {
getSynchronizer().endRead();
}
/**
* Notifies this configuration's {@link Synchronizer} that an update operation has finished. This method is called by
* all methods which modify this configuration at the end of their execution. Subclasses may override it to perform
* additional operations after an update. In any case the inherited method must be called! Otherwise, the write
* lock will not be released.
*
* @since 2.0
*/
protected void endWrite() {
getSynchronizer().endWrite();
}
/**
* Finds a {@code ConfigurationLookup} pointing to this configuration in the default lookups of the specified
* {@code ConfigurationInterpolator}. This method is called to ensure that there is exactly one default lookup querying
* this configuration.
*
* @param ci the {@code ConfigurationInterpolator} in question
* @return the found {@code Lookup} object or null
*/
private Lookup findConfigurationLookup(final ConfigurationInterpolator ci) {
return findConfigurationLookup(ci, this);
}
@Override
public T get(final Class cls, final String key) {
return convert(cls, key, null, true);
}
/**
* {@inheritDoc} This implementation delegates to the {@link ConversionHandler} to perform the actual type conversion.
*/
@Override
public T get(final Class cls, final String key, final T defaultValue) {
return convert(cls, key, defaultValue, false);
}
/**
* Obtains the property value for the specified key and converts it to the given target class.
*
* @param the target type of the conversion
* @param cls the target class
* @param key the key of the desired property
* @param defaultValue a default value
* @return the converted value of this property
* @throws ConversionException if the conversion cannot be performed
*/
private T getAndConvertProperty(final Class cls, final String key, final T defaultValue) {
final Object value = getProperty(key);
try {
return ObjectUtils.defaultIfNull(getConversionHandler().to(value, cls, getInterpolator()), defaultValue);
} catch (final ConversionException cex) {
// improve error message
throw new ConversionException(String.format("Key '%s' cannot be converted to class %s. Value is: '%s'.", key, cls.getName(), String.valueOf(value)),
cex.getCause());
}
}
@Override
public Object getArray(final Class> cls, final String key) {
return getArray(cls, key, null);
}
/**
* {@inheritDoc} This implementation delegates to the {@link ConversionHandler} to perform the actual type conversion.
* If this results in a null result (because the property is undefined), the default value is returned. It is
* checked whether the default value is an array with the correct component type. If not, an exception is thrown.
*
* @throws IllegalArgumentException if the default value is not a compatible array
*/
@Override
public Object getArray(final Class> cls, final String key, final Object defaultValue) {
return convertToArray(cls, key, defaultValue);
}
/**
* {@inheritDoc}
*
* @see #setThrowExceptionOnMissing(boolean)
*/
@Override
public BigDecimal getBigDecimal(final String key) {
return convert(BigDecimal.class, key, null, true);
}
@Override
public BigDecimal getBigDecimal(final String key, final BigDecimal defaultValue) {
return convert(BigDecimal.class, key, defaultValue, false);
}
/**
* {@inheritDoc}
*
* @see #setThrowExceptionOnMissing(boolean)
*/
@Override
public BigInteger getBigInteger(final String key) {
return convert(BigInteger.class, key, null, true);
}
@Override
public BigInteger getBigInteger(final String key, final BigInteger defaultValue) {
return convert(BigInteger.class, key, defaultValue, false);
}
@Override
public boolean getBoolean(final String key) {
final Boolean b = convert(Boolean.class, key, null, true);
return checkNonNullValue(key, b).booleanValue();
}
@Override
public boolean getBoolean(final String key, final boolean defaultValue) {
return getBoolean(key, Boolean.valueOf(defaultValue)).booleanValue();
}
/**
* Obtains the value of the specified key and tries to convert it into a {@code Boolean} object. If the property has no
* value, the passed in default value will be used.
*
* @param key the key of the property
* @param defaultValue the default value
* @return the value of this key converted to a {@code Boolean}
* @throws ConversionException if the value cannot be converted to a {@code Boolean}
*/
@Override
public Boolean getBoolean(final String key, final Boolean defaultValue) {
return convert(Boolean.class, key, defaultValue, false);
}
@Override
public byte getByte(final String key) {
final Byte b = convert(Byte.class, key, null, true);
return checkNonNullValue(key, b).byteValue();
}
@Override
public byte getByte(final String key, final byte defaultValue) {
return getByte(key, Byte.valueOf(defaultValue)).byteValue();
}
@Override
public Byte getByte(final String key, final Byte defaultValue) {
return convert(Byte.class, key, defaultValue, false);
}
@Override
public Collection getCollection(final Class cls, final String key, final Collection target) {
return getCollection(cls, key, target, null);
}
/**
* {@inheritDoc} This implementation delegates to the {@link ConversionHandler} to perform the actual conversion. If no
* target collection is provided, an {@code ArrayList} is created.
*/
@Override
public Collection getCollection(final Class cls, final String key, final Collection target, final Collection defaultValue) {
final Object src = getProperty(key);
if (src == null) {
return handleDefaultCollection(target, defaultValue);
}
final Collection targetCol = target != null ? target : new ArrayList<>();
getConversionHandler().toCollection(src, cls, getInterpolator(), targetCol);
return targetCol;
}
/**
* Gets the {@code ConfigurationDecoder} used by this instance.
*
* @return the {@code ConfigurationDecoder}
* @since 2.0
*/
public ConfigurationDecoder getConfigurationDecoder() {
return configurationDecoder;
}
/**
* Gets the {@code ConversionHandler} used by this instance.
*
* @return the {@code ConversionHandler}
* @since 2.0
*/
public ConversionHandler getConversionHandler() {
return conversionHandler;
}
@Override
public double getDouble(final String key) {
final Double d = convert(Double.class, key, null, true);
return checkNonNullValue(key, d).doubleValue();
}
@Override
public double getDouble(final String key, final double defaultValue) {
return getDouble(key, Double.valueOf(defaultValue)).doubleValue();
}
@Override
public Double getDouble(final String key, final Double defaultValue) {
return convert(Double.class, key, defaultValue, false);
}
@Override
public Duration getDuration(final String key) {
return checkNonNullValue(key, convert(Duration.class, key, null, true));
}
@Override
public Duration getDuration(final String key, final Duration defaultValue) {
return convert(Duration.class, key, defaultValue, false);
}
/**
* {@inheritDoc} This implementation makes use of the {@code ConfigurationDecoder} set for this configuration. If no
* such object has been set, an {@code IllegalStateException} exception is thrown.
*
* @throws IllegalStateException if no {@code ConfigurationDecoder} is set
* @see #setConfigurationDecoder(ConfigurationDecoder)
*/
@Override
public String getEncodedString(final String key) {
final ConfigurationDecoder decoder = getConfigurationDecoder();
if (decoder == null) {
throw new IllegalStateException("No default ConfigurationDecoder defined!");
}
return getEncodedString(key, decoder);
}
/**
* {@inheritDoc} This implementation delegates to {@link #getString(String)} in order to obtain the value of the passed
* in key. This value is passed to the decoder. Because {@code getString()} is used behind the scenes all standard
* features like handling of missing keys and interpolation work as expected.
*/
@Override
public String getEncodedString(final String key, final ConfigurationDecoder decoder) {
if (decoder == null) {
throw new IllegalArgumentException("ConfigurationDecoder must not be null!");
}
final String value = getString(key);
return value != null ? decoder.decode(value) : null;
}
@Override
public float getFloat(final String key) {
final Float f = convert(Float.class, key, null, true);
return checkNonNullValue(key, f).floatValue();
}
@Override
public float getFloat(final String key, final float defaultValue) {
return getFloat(key, Float.valueOf(defaultValue)).floatValue();
}
@Override
public Float getFloat(final String key, final Float defaultValue) {
return convert(Float.class, key, defaultValue, false);
}
@Override
public int getInt(final String key) {
final Integer i = convert(Integer.class, key, null, true);
return checkNonNullValue(key, i).intValue();
}
@Override
public int getInt(final String key, final int defaultValue) {
return getInteger(key, Integer.valueOf(defaultValue)).intValue();
}
@Override
public Integer getInteger(final String key, final Integer defaultValue) {
return convert(Integer.class, key, defaultValue, false);
}
/**
* Gets the {@code ConfigurationInterpolator} object that manages the lookup objects for resolving variables.
* Unless a custom interpolator has been set or the instance has been modified, the returned interpolator will
* resolve values from this configuration instance and support the
* {@link ConfigurationInterpolator#getDefaultPrefixLookups() default prefix lookups}.
*
* @return the {@code ConfigurationInterpolator} associated with this configuration
* @since 1.4
* @see ConfigurationInterpolator#getDefaultPrefixLookups()
*/
@Override
public ConfigurationInterpolator getInterpolator() {
return interpolator.get();
}
/**
* {@inheritDoc} This implementation takes care of synchronization and then delegates to {@code getKeysInternal()} for
* obtaining the actual iterator. Note that depending on a concrete implementation, an iteration may fail if the
* configuration is updated concurrently.
*/
@Override
public final Iterator getKeys() {
beginRead(false);
try {
return getKeysInternal();
} finally {
endRead();
}
}
/**
* {@inheritDoc} This implementation returns keys that either match the prefix or start with the prefix followed by a
* dot ('.'). So the call {@code getKeys("db");} will find the keys {@code db}, {@code db.user}, or {@code db.password},
* but not the key {@code dbdriver}.
*/
@Override
public final Iterator getKeys(final String prefix) {
beginRead(false);
try {
return getKeysInternal(prefix);
} finally {
endRead();
}
}
/**
* {@inheritDoc} This implementation returns keys that either match the prefix or start with the prefix followed by the delimiter.
* So the call {@code getKeys("db");} will find the keys {@code db}, {@code db@user}, or {@code db@password},
* but not the key {@code dbdriver}.
*/
@Override
public final Iterator getKeys(final String prefix, final String delimiter) {
beginRead(false);
try {
return getKeysInternal(prefix, delimiter);
} finally {
endRead();
}
}
/**
* Actually creates an iterator for iterating over the keys in this configuration. This method is called by
* {@code getKeys()}, it has to be defined by concrete subclasses.
*
* @return an {@code Iterator} with all property keys in this configuration
* @since 2.0
*/
protected abstract Iterator getKeysInternal();
/**
* Gets an {@code Iterator} with all property keys starting with the specified prefix. This method is called by
* {@link #getKeys(String)}. It is fully implemented by delegating to {@code getKeysInternal()} and returning a special
* iterator which filters for the passed in prefix. Subclasses can override it if they can provide a more efficient way
* to iterate over specific keys only.
*
* @param prefix the prefix for the keys to be taken into account
* @return an {@code Iterator} returning the filtered keys
* @since 2.0
*/
protected Iterator getKeysInternal(final String prefix) {
return new PrefixedKeysIterator(getKeysInternal(), prefix);
}
/**
* Gets an {@code Iterator} with all property keys starting with the specified prefix and specified delimiter. This method is called by
* {@link #getKeys(String)}. It is fully implemented by delegating to {@code getKeysInternal()} and returning a special
* iterator which filters for the passed in prefix. Subclasses can override it if they can provide a more efficient way
* to iterate over specific keys only.
*
* @param prefix the prefix for the keys to be taken into account
* @param delimiter the prefix delimiter
* @return an {@code Iterator} returning the filtered keys
* @since 2.10.0
*/
protected Iterator getKeysInternal(final String prefix, final String delimiter) {
return new PrefixedKeysIterator(getKeysInternal(), prefix, delimiter);
}
@Override
public List getList(final Class cls, final String key) {
return getList(cls, key, null);
}
/**
* {@inheritDoc} This implementation delegates to the generic {@code getCollection()}. As target collection a newly
* created {@code ArrayList} is passed in.
*/
@Override
public List getList(final Class cls, final String key, final List defaultValue) {
final List result = new ArrayList<>();
if (getCollection(cls, key, result, defaultValue) == null) {
return null;
}
return result;
}
/**
* {@inheritDoc}
*
* @see #getStringArray(String)
*/
@Override
public List