All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.commons.configuration2.AbstractConfiguration Maven / Gradle / Ivy

Go to download

Tools to assist in the reading of configuration/preferences files in various formats

The newest version!
/*
 * 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 getList(final String key) { return getList(key, new ArrayList<>()); } @Override public List getList(final String key, final List defaultValue) { final Object value = getProperty(key); final List list; if (value instanceof String) { list = new ArrayList<>(1); list.add(interpolate((String) value)); } else if (value instanceof List) { list = new ArrayList<>(); final List l = (List) value; // add the interpolated elements in the new list l.forEach(elem -> list.add(interpolate(elem))); } else if (value == null) { // This is okay because we just return this list to the caller @SuppressWarnings("unchecked") final List resultList = (List) defaultValue; list = resultList; } else if (value.getClass().isArray()) { return Arrays.asList((Object[]) value); } else if (isScalarValue(value)) { return Collections.singletonList((Object) value.toString()); } else { throw new ConversionException('\'' + key + "' doesn't map to a List object: " + value + ", a " + value.getClass().getName()); } return list; } /** * Gets the {@code ListDelimiterHandler} used by this instance. * * @return the {@code ListDelimiterHandler} * @since 2.0 */ public ListDelimiterHandler getListDelimiterHandler() { return listDelimiterHandler; } /** * Gets the logger used by this configuration object. * * @return the logger * @since 2.0 */ public ConfigurationLogger getLogger() { return log; } @Override public long getLong(final String key) { final Long l = convert(Long.class, key, null, true); return checkNonNullValue(key, l).longValue(); } @Override public long getLong(final String key, final long defaultValue) { return getLong(key, Long.valueOf(defaultValue)).longValue(); } @Override public Long getLong(final String key, final Long defaultValue) { return convert(Long.class, key, defaultValue, false); } @Override public Properties getProperties(final String key) { return getProperties(key, null); } /** * Gets a list of properties associated with the given configuration key. * * @param key The configuration key. * @param defaults Any default values for the returned {@code Properties} object. Ignored if {@code null}. * * @return The associated properties if key is found. * * @throws ConversionException is thrown if the key maps to an object that is not a String/List of Strings. * * @throws IllegalArgumentException if one of the tokens is malformed (does not contain an equals sign). */ public Properties getProperties(final String key, final Properties defaults) { /* * Grab an array of the tokens for this key. */ final String[] tokens = getStringArray(key); /* * Each token is of the form 'key=value'. */ final Properties props = defaults == null ? new Properties() : new Properties(defaults); for (final String token : tokens) { final int equalSign = token.indexOf('='); if (equalSign > 0) { final String pkey = token.substring(0, equalSign).trim(); final String pvalue = token.substring(equalSign + 1).trim(); props.put(pkey, pvalue); } else if (tokens.length == 1 && StringUtils.isEmpty(key)) { // Semantically equivalent to an empty Properties // object. break; } else { throw new IllegalArgumentException('\'' + token + "' does not contain an equals sign"); } } return props; } /** * {@inheritDoc} This implementation ensures proper synchronization. Subclasses have to define the abstract * {@code getPropertyInternal()} method which is called from here. */ @Override public final Object getProperty(final String key) { beginRead(false); try { return getPropertyInternal(key); } finally { endRead(); } } /** * Actually obtains the value of the specified property. This method is called by {@code getProperty()}. Concrete * subclasses must define it to fetch the value of the desired property. * * @param key the key of the property in question * @return the (raw) value of this property * @since 2.0 */ protected abstract Object getPropertyInternal(String key); @Override public short getShort(final String key) { final Short s = convert(Short.class, key, null, true); return checkNonNullValue(key, s).shortValue(); } @Override public short getShort(final String key, final short defaultValue) { return getShort(key, Short.valueOf(defaultValue)).shortValue(); } @Override public Short getShort(final String key, final Short defaultValue) { return convert(Short.class, key, defaultValue, false); } /** * {@inheritDoc} * * @see #setThrowExceptionOnMissing(boolean) */ @Override public String getString(final String key) { return convert(String.class, key, null, true); } @Override public String getString(final String key, final String defaultValue) { final String result = convert(String.class, key, null, false); return result != null ? result : interpolate(defaultValue); } /** * Gets an array of strings associated with the given configuration key. If the key doesn't map to an existing object, an * empty array is returned. When a property is added to a configuration, it is checked whether it contains multiple * values. This is obvious if the added object is a list or an array. For strings the association * {@link ListDelimiterHandler} is consulted to find out whether the string can be split into multiple values. * * @param key The configuration key. * @return The associated string array if key is found. * * @throws ConversionException is thrown if the key maps to an object that is not a String/List of Strings. * @see #setListDelimiterHandler(ListDelimiterHandler) */ @Override public String[] getStringArray(final String key) { final String[] result = (String[]) getArray(String.class, key); return result == null ? ArrayUtils.EMPTY_STRING_ARRAY : result; } /** * Gets the object responsible for synchronizing this configuration. All access to this configuration - both read and * write access - is controlled by this object. This implementation never returns null. If no * {@code Synchronizer} has been set, a {@link NoOpSynchronizer} is returned. So, per default, instances of * {@code AbstractConfiguration} are not thread-safe unless a suitable {@code Synchronizer} is set! * * @return the {@code Synchronizer} used by this instance * @since 2.0 */ @Override public final Synchronizer getSynchronizer() { final Synchronizer sync = synchronizer; return sync != null ? sync : NoOpSynchronizer.INSTANCE; } @Override public ImmutableConfiguration immutableSubset(final String prefix) { return ConfigurationUtils.unmodifiableConfiguration(subset(prefix)); } /** * Initializes the logger. Supports null input. This method can be called by derived classes in order to enable * logging. * * @param log the logger * @since 2.0 */ protected final void initLogger(final ConfigurationLogger log) { this.log = log != null ? log : ConfigurationLogger.newDummyLogger(); } /** * Creates a default {@code ConfigurationInterpolator} which is initialized with all default {@code Lookup} objects. * This method is called by the constructor. It ensures that default interpolation works for every new configuration * instance. */ private void installDefaultInterpolator() { installInterpolator(ConfigurationInterpolator.getDefaultPrefixLookups(), null); } /** * {@inheritDoc} This implementation creates a new {@code ConfigurationInterpolator} instance and initializes it with * the given {@code Lookup} objects. In addition, it adds a specialized default {@code Lookup} object which queries this * {@code Configuration}. * * @since 2.0 */ @Override public final void installInterpolator(final Map prefixLookups, final Collection defLookups) { final InterpolatorSpecification spec = new InterpolatorSpecification.Builder().withPrefixLookups(prefixLookups).withDefaultLookups(defLookups) .withDefaultLookup(new ConfigurationLookup(this)).create(); setInterpolator(ConfigurationInterpolator.fromSpecification(spec)); } /** * Returns the interpolated value. This implementation delegates to the current {@code ConfigurationInterpolator}. If no * {@code ConfigurationInterpolator} is set, the passed in value is returned without changes. * * @param value the value to interpolate * @return the value with variables substituted */ protected Object interpolate(final Object value) { final ConfigurationInterpolator ci = getInterpolator(); return ci != null ? ci.interpolate(value) : value; } /** * interpolate key names to handle ${key} stuff * * @param base string to interpolate * * @return returns the key name with the ${key} substituted */ protected String interpolate(final String base) { return Objects.toString(interpolate((Object) base), null); } /** * Returns a configuration with the same content as this configuration, but with all variables replaced by their actual * values. This method tries to clone the configuration and then perform interpolation on all properties. So property * values of the form {@code ${var}} will be resolved as far as possible (if a variable cannot be resolved, it remains * unchanged). This operation is useful if the content of a configuration is to be exported or processed by an external * component that does not support variable interpolation. * * @return a configuration with all variables interpolated * @throws org.apache.commons.configuration2.ex.ConfigurationRuntimeException if this configuration cannot be cloned * @since 1.5 */ public Configuration interpolatedConfiguration() { // first clone this configuration final AbstractConfiguration c = (AbstractConfiguration) ConfigurationUtils.cloneConfiguration(this); // now perform interpolation c.setListDelimiterHandler(new DisabledListDelimiterHandler()); getKeys().forEachRemaining(key -> c.setProperty(key, getList(key))); c.setListDelimiterHandler(getListDelimiterHandler()); return c; } /** * {@inheritDoc} This implementation handles synchronization and delegates to {@code isEmptyInternal()}. */ @Override public final boolean isEmpty() { beginRead(false); try { return isEmptyInternal(); } finally { endRead(); } } /** * Actually checks whether this configuration contains data. This method is called by {@code isEmpty()}. It has to be * defined by concrete subclasses. * * @return true if this configuration contains no data, false otherwise * @since 2.0 */ protected abstract boolean isEmptyInternal(); /** * Checks whether the specified object is a scalar value. This method is called by {@code getList()} and * {@code getStringArray()} if the property requested is not a string, a list, or an array. If it returns true, * the calling method transforms the value to a string and returns a list or an array with this single element. This * implementation returns true if the value is of a wrapper type for a primitive type. * * @param value the value to be checked * @return a flag whether the value is a scalar * @since 1.7 */ protected boolean isScalarValue(final Object value) { return ClassUtils.wrapperToPrimitive(value.getClass()) != null; } /** * Returns true if missing values throw Exceptions. * * @return true if missing values throw Exceptions */ public boolean isThrowExceptionOnMissing() { return throwExceptionOnMissing; } /** * {@inheritDoc} This implementation delegates to {@code beginRead()} or {@code beginWrite()}, depending on the * {@code LockMode} argument. Subclasses can override these protected methods to perform additional steps when a * configuration is locked. * * @since 2.0 * @throws NullPointerException if the argument is null */ @Override public final void lock(final LockMode mode) { switch (mode) { case READ: beginRead(false); break; case WRITE: beginWrite(false); break; default: throw new IllegalArgumentException("Unsupported LockMode: " + mode); } } /** * Sets the {@code ConfigurationDecoder} for this configuration. This object is used by * {@link #getEncodedString(String)}. * * @param configurationDecoder the {@code ConfigurationDecoder} * @since 2.0 */ public void setConfigurationDecoder(final ConfigurationDecoder configurationDecoder) { this.configurationDecoder = configurationDecoder; } /** * Sets the {@code ConversionHandler} to be used by this instance. The {@code ConversionHandler} is responsible for * every kind of data type conversion. It is consulted by all get methods returning results in specific data types. A * newly created configuration uses a default {@code ConversionHandler} implementation. This can be changed while * initializing the configuration (e.g. via a builder). Note that access to this property is not synchronized. * * @param conversionHandler the {@code ConversionHandler} to be used (must not be null) * @throws IllegalArgumentException if the {@code ConversionHandler} is null * @since 2.0 */ public void setConversionHandler(final ConversionHandler conversionHandler) { if (conversionHandler == null) { throw new IllegalArgumentException("ConversionHandler must not be null!"); } this.conversionHandler = conversionHandler; } /** * Adds all {@code Lookup} objects in the given collection as default lookups (i.e. lookups without a variable prefix) * to the {@code ConfigurationInterpolator} object of this configuration. In addition, it adds a specialized default * {@code Lookup} object which queries this {@code Configuration}. The set of {@code Lookup} objects with prefixes is * not modified by this method. If this configuration does not have a {@code ConfigurationInterpolator}, a new instance * is created. Note: This method is mainly intended to be used for initializing a configuration when it is created by a * builder. Normal client code should better call {@link #installInterpolator(Map, Collection)} to define the * {@code ConfigurationInterpolator} in a single step. * * @param lookups the collection with default {@code Lookup} objects to be added * @since 2.0 */ public void setDefaultLookups(final Collection lookups) { boolean success; do { final ConfigurationInterpolator ciOld = getInterpolator(); final ConfigurationInterpolator ciNew = ciOld != null ? ciOld : new ConfigurationInterpolator(); Lookup confLookup = findConfigurationLookup(ciNew); if (confLookup == null) { confLookup = new ConfigurationLookup(this); } else { ciNew.removeDefaultLookup(confLookup); } ciNew.addDefaultLookups(lookups); ciNew.addDefaultLookup(confLookup); success = interpolator.compareAndSet(ciOld, ciNew); } while (!success); } /** * {@inheritDoc} This implementation sets the passed in object without further modifications. A null argument is * allowed; this disables interpolation. * * @since 2.0 */ @Override public final void setInterpolator(final ConfigurationInterpolator ci) { interpolator.set(ci); } /** *

* Sets the {@code ListDelimiterHandler} to be used by this instance. This object is invoked every time when dealing * with string properties that may contain a list delimiter and thus have to be split to multiple values. Per default, a * {@code ListDelimiterHandler} implementation is set which does not support list splitting. This can be changed for * instance by setting a {@link org.apache.commons.configuration2.convert.DefaultListDelimiterHandler * DefaultListDelimiterHandler} object. *

*

* Warning: Be careful when changing the list delimiter handler when the configuration has already been * loaded/populated. List handling is typically applied already when properties are added to the configuration. If later * another handler is set which processes lists differently, results may be unexpected; some operations may even cause * exceptions. *

* * @param listDelimiterHandler the {@code ListDelimiterHandler} to be used (must not be null) * @throws IllegalArgumentException if the {@code ListDelimiterHandler} is null * @since 2.0 */ public void setListDelimiterHandler(final ListDelimiterHandler listDelimiterHandler) { if (listDelimiterHandler == null) { throw new IllegalArgumentException("List delimiter handler must not be null!"); } this.listDelimiterHandler = listDelimiterHandler; } /** * Allows setting the logger to be used by this configuration object. This method makes it possible for clients to * exactly control logging behavior. Per default a logger is set that will ignore all log messages. Derived classes that * want to enable logging should call this method during their initialization with the logger to be used. It is legal to * pass a null logger; in this case, logging will be disabled. * * @param log the new logger * @since 2.0 */ public void setLogger(final ConfigurationLogger log) { initLogger(log); } /** * Sets the specified {@code ConfigurationInterpolator} as the parent of this configuration's * {@code ConfigurationInterpolator}. If this configuration does not have a {@code ConfigurationInterpolator}, a new * instance is created. Note: This method is mainly intended to be used for initializing a configuration when it is * created by a builder. Normal client code can directly update the {@code ConfigurationInterpolator}. * * @param parent the parent {@code ConfigurationInterpolator} to be set * @since 2.0 */ public void setParentInterpolator(final ConfigurationInterpolator parent) { boolean success; do { final ConfigurationInterpolator ciOld = getInterpolator(); final ConfigurationInterpolator ciNew = ciOld != null ? ciOld : new ConfigurationInterpolator(); ciNew.setParentInterpolator(parent); success = interpolator.compareAndSet(ciOld, ciNew); } while (!success); } /** * Registers all {@code Lookup} objects in the given map at the current {@code ConfigurationInterpolator} of this * configuration. The set of default lookup objects (for variables without a prefix) is not modified by this method. If * this configuration does not have a {@code ConfigurationInterpolator}, a new instance is created. Note: This method is * mainly intended to be used for initializing a configuration when it is created by a builder. Normal client code * should better call {@link #installInterpolator(Map, Collection)} to define the {@code ConfigurationInterpolator} in a * single step. * * @param lookups a map with new {@code Lookup} objects and their prefixes (may be null) * @since 2.0 */ public void setPrefixLookups(final Map lookups) { boolean success; do { // do this in a loop because the ConfigurationInterpolator // instance may be changed by another thread final ConfigurationInterpolator ciOld = getInterpolator(); final ConfigurationInterpolator ciNew = ciOld != null ? ciOld : new ConfigurationInterpolator(); ciNew.registerLookups(lookups); success = interpolator.compareAndSet(ciOld, ciNew); } while (!success); } @Override public final void setProperty(final String key, final Object value) { beginWrite(false); try { fireEvent(ConfigurationEvent.SET_PROPERTY, key, value, true); setPropertyInternal(key, value); fireEvent(ConfigurationEvent.SET_PROPERTY, key, value, false); } finally { endWrite(); } } /** * Actually sets the value of a property. This method is called by {@code setProperty()}. It provides a default * implementation of this functionality by clearing the specified key and delegating to {@code addProperty()}. * Subclasses should override this method if they can provide a more efficient algorithm for setting a property value. * * @param key the property key * @param value the new property value * @since 2.0 */ protected void setPropertyInternal(final String key, final Object value) { setDetailEvents(false); try { clearProperty(key); addProperty(key, value); } finally { setDetailEvents(true); } } /** * Sets the object responsible for synchronizing this configuration. This method has to be called with a suitable * {@code Synchronizer} object when initializing this configuration instance in order to make it thread-safe. * * @param synchronizer the new {@code Synchronizer}; can be null, then this instance uses a * {@link NoOpSynchronizer} * @since 2.0 */ @Override public final void setSynchronizer(final Synchronizer synchronizer) { this.synchronizer = synchronizer; } /** * Allows to set the {@code throwExceptionOnMissing} flag. This flag controls the behavior of property getter methods * that return objects if the requested property is missing. If the flag is set to false (which is the default * value), these methods will return null. If set to true, they will throw a * {@code NoSuchElementException} exception. Note that getter methods for primitive data types are not affected by this * flag. * * @param throwExceptionOnMissing The new value for the property */ public void setThrowExceptionOnMissing(final boolean throwExceptionOnMissing) { this.throwExceptionOnMissing = throwExceptionOnMissing; } /** * {@inheritDoc} This implementation handles synchronization and delegates to {@code sizeInternal()}. */ @Override public final int size() { beginRead(false); try { return sizeInternal(); } finally { endRead(); } } /** * Actually calculates the size of this configuration. This method is called by {@code size()} with a read lock held. * The base implementation provided here calculates the size based on the iterator returned by {@code getKeys()}. Sub * classes which can determine the size in a more efficient way should override this method. * * @return the size of this configuration (i.e. the number of keys) */ protected int sizeInternal() { int size = 0; for (final Iterator keyIt = getKeysInternal(); keyIt.hasNext(); size++) { keyIt.next(); } return size; } @Override public Configuration subset(final String prefix) { return new SubsetConfiguration(this, prefix, "."); } /** * {@inheritDoc} This implementation delegates to {@code endRead()} or {@code endWrite()}, depending on the * {@code LockMode} argument. Subclasses can override these protected methods to perform additional steps when a * configuration's lock is released. * * @throws NullPointerException if the argument is null */ @Override public final void unlock(final LockMode mode) { switch (mode) { case READ: endRead(); break; case WRITE: endWrite(); break; default: throw new IllegalArgumentException("Unsupported LockMode: " + mode); } } }