/*
 * 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.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.configuration2.event.ConfigurationEvent;
import org.apache.commons.configuration2.event.EventListener;
import org.apache.commons.configuration2.event.EventSource;
import org.apache.commons.configuration2.event.EventType;
import org.apache.commons.configuration2.ex.ConfigurationRuntimeException;
import org.apache.commons.configuration2.sync.LockMode;
import org.apache.commons.configuration2.tree.DefaultConfigurationKey;
import org.apache.commons.configuration2.tree.DefaultExpressionEngine;
import org.apache.commons.configuration2.tree.ExpressionEngine;
import org.apache.commons.configuration2.tree.ImmutableNode;
import org.apache.commons.configuration2.tree.NodeCombiner;
import org.apache.commons.configuration2.tree.NodeTreeWalker;
import org.apache.commons.configuration2.tree.QueryResult;
import org.apache.commons.configuration2.tree.TreeUtils;
import org.apache.commons.configuration2.tree.UnionCombiner;

A hierarchical composite configuration class.

This class maintains a list of configuration objects, which can be added using the diverse addConfiguration() methods. After that the configurations can be accessed either by name (if one was provided when the configuration was added) or by index. For the whole set of managed configurations a logical node structure is constructed. For this purpose a NodeCombiner object can be set. This makes it possible to specify different algorithms for the combination process.

The big advantage of this class is that it creates a truly hierarchical structure of all the properties stored in the contained configurations - even if some of them are no hierarchical configurations per se. So all enhanced features provided by a hierarchical configuration (e.g. choosing an expression engine) are applicable.

The class works by registering itself as an event listener at all added configurations. So it gets notified whenever one of these configurations is changed and can invalidate its internal node structure. The next time a property is accessed the node structure will be re-constructed using the current state of the managed configurations. Note that, depending on the used NodeCombiner, this may be a complex operation.

Because of the way a CombinedConfiguration is working it has more or less view character: it provides a logic view on the configurations it contains. In this constellation not all methods defined for hierarchical configurations - especially methods that update the stored properties - can be implemented in a consistent manner. Using such methods (like addProperty(), or clearProperty() on a CombinedConfiguration is not strictly forbidden, however, depending on the current NodeCombiner and the involved properties, the results may be different than expected. Some examples may illustrate this:

  • Imagine a CombinedConfiguration cc containing two child configurations with the following content:
    user.properties
    gui.background = blue
    gui.position = (10, 10, 400, 200)
    
    default.properties
    gui.background = black
    gui.foreground = white
    home.dir = /data
    
    As a NodeCombiner a OverrideCombiner is used. This combiner will ensure that defined user settings take precedence over the default values. If the resulting CombinedConfiguration is queried for the background color, blue will be returned because this value is defined in user.properties. Now consider what happens if the key gui.background is removed from the CombinedConfiguration:
    cc.clearProperty("gui.background");
    
    Will a cc.containsKey("gui.background") now return false? No, it won't! The clearProperty() operation is executed on the node set of the combined configuration, which was constructed from the nodes of the two child configurations. It causes the value of the background node to be cleared, which is also part of the first child configuration. This modification of one of its child configurations causes the CombinedConfiguration to be re-constructed. This time the OverrideCombiner cannot find a gui.background property in the first child configuration, but it finds one in the second, and adds it to the resulting combined configuration. So the property is still present (with a different value now).
  • addProperty() can also be problematic: Most node combiners use special view nodes for linking parts of the original configurations' data together. If new properties are added to such a special node, they do not belong to any of the managed configurations and thus hang in the air. Using the same configurations as in the last example, the statement
    addProperty("database.user", "scott");
    
    would cause such a hanging property. If now one of the child configurations is changed and the CombinedConfiguration is re-constructed, this property will disappear! (Add operations are not problematic if they result in a child configuration being updated. For instance an addProperty("home.url", "localhost"); will alter the second child configuration - because the prefix home is here already present; when the CombinedConfiguration is re-constructed, this change is taken into account.)

Because of such problems it is recommended to perform updates only on the managed child configurations.

Whenever the node structure of a CombinedConfiguration becomes invalid (either because one of the contained configurations was modified or because the invalidate() method was directly called) an event is generated. So this can be detected by interested event listeners. This also makes it possible to add a combined configuration into another one.

Notes about thread-safety: This configuration implementation uses a Synchronizer object to protect instances against concurrent access. The concrete Synchronizer implementation used determines whether an instance of this class is thread-safe or not. In contrast to other implementations derived from BaseHierarchicalConfiguration, thread-safety is an issue here because the nodes structure used by this configuration has to be constructed dynamically when a child configuration is changed. Therefore, when multiple threads are involved which also manipulate one of the child configurations, a proper Synchronizer object should be set. Note that the Synchronizer objects used by the child configurations do not really matter. Because immutable in-memory nodes structures are used for them there is no danger that updates on child configurations could interfere with read operations on the combined configuration.

Since:1.3
/** * <p> * A hierarchical composite configuration class. * </p> * <p> * This class maintains a list of configuration objects, which can be added * using the diverse {@code addConfiguration()} methods. After that the * configurations can be accessed either by name (if one was provided when the * configuration was added) or by index. For the whole set of managed * configurations a logical node structure is constructed. For this purpose a * {@link org.apache.commons.configuration2.tree.NodeCombiner NodeCombiner} * object can be set. This makes it possible to specify different algorithms for * the combination process. * </p> * <p> * The big advantage of this class is that it creates a truly hierarchical * structure of all the properties stored in the contained configurations - even * if some of them are no hierarchical configurations per se. So all enhanced * features provided by a hierarchical configuration (e.g. choosing an * expression engine) are applicable. * </p> * <p> * The class works by registering itself as an event listener at all added * configurations. So it gets notified whenever one of these configurations is * changed and can invalidate its internal node structure. The next time a * property is accessed the node structure will be re-constructed using the * current state of the managed configurations. Note that, depending on the used * {@code NodeCombiner}, this may be a complex operation. * </p> * <p> * Because of the way a {@code CombinedConfiguration} is working it has more or * less view character: it provides a logic view on the configurations it * contains. In this constellation not all methods defined for hierarchical * configurations - especially methods that update the stored properties - can * be implemented in a consistent manner. Using such methods (like * {@code addProperty()}, or {@code clearProperty()} on a * {@code CombinedConfiguration} is not strictly forbidden, however, depending * on the current {@link NodeCombiner} and the involved properties, the results * may be different than expected. Some examples may illustrate this: * </p> * <ul> * <li>Imagine a {@code CombinedConfiguration} <em>cc</em> containing two child * configurations with the following content: * <dl> * <dt>user.properties</dt> * <dd> * * <pre> * gui.background = blue * gui.position = (10, 10, 400, 200) * </pre> * * </dd> * <dt>default.properties</dt> * <dd> * * <pre> * gui.background = black * gui.foreground = white * home.dir = /data * </pre> * * </dd> * </dl> * As a {@code NodeCombiner} a * {@link org.apache.commons.configuration2.tree.OverrideCombiner * OverrideCombiner} is used. This combiner will ensure that defined user * settings take precedence over the default values. If the resulting * {@code CombinedConfiguration} is queried for the background color, * {@code blue} will be returned because this value is defined in * {@code user.properties}. Now consider what happens if the key * {@code gui.background} is removed from the {@code CombinedConfiguration}: * * <pre> * cc.clearProperty(&quot;gui.background&quot;); * </pre> * * Will a {@code cc.containsKey("gui.background")} now return <b>false</b>? No, * it won't! The {@code clearProperty()} operation is executed on the node set * of the combined configuration, which was constructed from the nodes of the * two child configurations. It causes the value of the <em>background</em> node * to be cleared, which is also part of the first child configuration. This * modification of one of its child configurations causes the * {@code CombinedConfiguration} to be re-constructed. This time the * {@code OverrideCombiner} cannot find a {@code gui.background} property in the * first child configuration, but it finds one in the second, and adds it to the * resulting combined configuration. So the property is still present (with a * different value now).</li> * <li>{@code addProperty()} can also be problematic: Most node combiners use * special view nodes for linking parts of the original configurations' data * together. If new properties are added to such a special node, they do not * belong to any of the managed configurations and thus hang in the air. Using * the same configurations as in the last example, the statement * * <pre> * addProperty(&quot;database.user&quot;, &quot;scott&quot;); * </pre> * * would cause such a hanging property. If now one of the child configurations * is changed and the {@code CombinedConfiguration} is re-constructed, this * property will disappear! (Add operations are not problematic if they result * in a child configuration being updated. For instance an * {@code addProperty("home.url", "localhost");} will alter the second child * configuration - because the prefix <em>home</em> is here already present; * when the {@code CombinedConfiguration} is re-constructed, this change is * taken into account.)</li> * </ul> * <p> * Because of such problems it is recommended to perform updates only on the * managed child configurations. * </p> * <p> * Whenever the node structure of a {@code CombinedConfiguration} becomes * invalid (either because one of the contained configurations was modified or * because the {@code invalidate()} method was directly called) an event is * generated. So this can be detected by interested event listeners. This also * makes it possible to add a combined configuration into another one. * </p> * <p> * Notes about thread-safety: This configuration implementation uses a * {@code Synchronizer} object to protect instances against concurrent access. * The concrete {@code Synchronizer} implementation used determines whether an * instance of this class is thread-safe or not. In contrast to other * implementations derived from {@link BaseHierarchicalConfiguration}, * thread-safety is an issue here because the nodes structure used by this * configuration has to be constructed dynamically when a child configuration is * changed. Therefore, when multiple threads are involved which also manipulate * one of the child configurations, a proper {@code Synchronizer} object should * be set. Note that the {@code Synchronizer} objects used by the child * configurations do not really matter. Because immutable in-memory nodes * structures are used for them there is no danger that updates on child * configurations could interfere with read operations on the combined * configuration. * </p> * * @since 1.3 */
public class CombinedConfiguration extends BaseHierarchicalConfiguration implements EventListener<ConfigurationEvent> {
Constant for the event type fired when the internal node structure of a combined configuration becomes invalid.
Since:2.0
/** * Constant for the event type fired when the internal node structure of a * combined configuration becomes invalid. * * @since 2.0 */
public static final EventType<ConfigurationEvent> COMBINED_INVALIDATE = new EventType<>(ConfigurationEvent.ANY, "COMBINED_INVALIDATE");
Constant for the expression engine for parsing the at path.
/** Constant for the expression engine for parsing the at path. */
private static final DefaultExpressionEngine AT_ENGINE = DefaultExpressionEngine.INSTANCE;
Constant for the default node combiner.
/** Constant for the default node combiner. */
private static final NodeCombiner DEFAULT_COMBINER = new UnionCombiner();
Constant for a root node for an empty configuration.
/** Constant for a root node for an empty configuration. */
private static final ImmutableNode EMPTY_ROOT = new ImmutableNode.Builder() .create();
Stores the combiner.
/** Stores the combiner. */
private NodeCombiner nodeCombiner;
Stores a list with the contained configurations.
/** Stores a list with the contained configurations. */
private List<ConfigData> configurations;
Stores a map with the named configurations.
/** Stores a map with the named configurations. */
private Map<String, Configuration> namedConfigurations;
An expression engine used for converting child configurations to hierarchical ones.
/** * An expression engine used for converting child configurations to * hierarchical ones. */
private ExpressionEngine conversionExpressionEngine;
A flag whether this configuration is up-to-date.
/** A flag whether this configuration is up-to-date. */
private boolean upToDate;
Creates a new instance of CombinedConfiguration and initializes the combiner to be used.
Params:
  • comb – the node combiner (can be null, then a union combiner is used as default)
/** * Creates a new instance of {@code CombinedConfiguration} and * initializes the combiner to be used. * * @param comb the node combiner (can be <b>null</b>, then a union combiner * is used as default) */
public CombinedConfiguration(final NodeCombiner comb) { nodeCombiner = comb != null ? comb : DEFAULT_COMBINER; initChildCollections(); }
Creates a new instance of CombinedConfiguration that uses a union combiner.
See Also:
/** * Creates a new instance of {@code CombinedConfiguration} that uses * a union combiner. * * @see org.apache.commons.configuration2.tree.UnionCombiner */
public CombinedConfiguration() { this(null); }
Returns the node combiner that is used for creating the combined node structure.
Returns:the node combiner
/** * Returns the node combiner that is used for creating the combined node * structure. * * @return the node combiner */
public NodeCombiner getNodeCombiner() { beginRead(true); try { return nodeCombiner; } finally { endRead(); } }
Sets the node combiner. This object will be used when the combined node structure is to be constructed. It must not be null, otherwise an IllegalArgumentException exception is thrown. Changing the node combiner causes an invalidation of this combined configuration, so that the new combiner immediately takes effect.
Params:
  • nodeCombiner – the node combiner
/** * Sets the node combiner. This object will be used when the combined node * structure is to be constructed. It must not be <b>null</b>, otherwise an * {@code IllegalArgumentException} exception is thrown. Changing the * node combiner causes an invalidation of this combined configuration, so * that the new combiner immediately takes effect. * * @param nodeCombiner the node combiner */
public void setNodeCombiner(final NodeCombiner nodeCombiner) { if (nodeCombiner == null) { throw new IllegalArgumentException( "Node combiner must not be null!"); } beginWrite(true); try { this.nodeCombiner = nodeCombiner; invalidateInternal(); } finally { endWrite(); } }
Returns the ExpressionEngine for converting flat child configurations to hierarchical ones.
Returns:the conversion expression engine
Since:1.6
/** * Returns the {@code ExpressionEngine} for converting flat child * configurations to hierarchical ones. * * @return the conversion expression engine * @since 1.6 */
public ExpressionEngine getConversionExpressionEngine() { beginRead(true); try { return conversionExpressionEngine; } finally { endRead(); } }
Sets the ExpressionEngine for converting flat child configurations to hierarchical ones. When constructing the root node for this combined configuration the properties of all child configurations must be combined to a single hierarchical node structure. In this process, non hierarchical configurations are converted to hierarchical ones first. This can be problematic if a child configuration contains keys that are no compatible with the default expression engine used by hierarchical configurations. Therefore it is possible to specify a specific expression engine to be used for this purpose.
Params:
  • conversionExpressionEngine – the conversion expression engine
See Also:
Since:1.6
/** * Sets the {@code ExpressionEngine} for converting flat child * configurations to hierarchical ones. When constructing the root node for * this combined configuration the properties of all child configurations * must be combined to a single hierarchical node structure. In this * process, non hierarchical configurations are converted to hierarchical * ones first. This can be problematic if a child configuration contains * keys that are no compatible with the default expression engine used by * hierarchical configurations. Therefore it is possible to specify a * specific expression engine to be used for this purpose. * * @param conversionExpressionEngine the conversion expression engine * @see ConfigurationUtils#convertToHierarchical(Configuration, ExpressionEngine) * @since 1.6 */
public void setConversionExpressionEngine( final ExpressionEngine conversionExpressionEngine) { beginWrite(true); try { this.conversionExpressionEngine = conversionExpressionEngine; } finally { endWrite(); } }
Adds a new configuration to this combined configuration. It is possible (but not mandatory) to give the new configuration a name. This name must be unique, otherwise a ConfigurationRuntimeException will be thrown. With the optional at argument you can specify where in the resulting node structure the content of the added configuration should appear. This is a string that uses dots as property delimiters (independent on the current expression engine). For instance if you pass in the string "database.tables", all properties of the added configuration will occur in this branch.
Params:
  • config – the configuration to add (must not be null)
  • name – the name of this configuration (can be null)
  • at – the position of this configuration in the combined tree (can be null)
/** * Adds a new configuration to this combined configuration. It is possible * (but not mandatory) to give the new configuration a name. This name must * be unique, otherwise a {@code ConfigurationRuntimeException} will * be thrown. With the optional {@code at} argument you can specify * where in the resulting node structure the content of the added * configuration should appear. This is a string that uses dots as property * delimiters (independent on the current expression engine). For instance * if you pass in the string {@code "database.tables"}, * all properties of the added configuration will occur in this branch. * * @param config the configuration to add (must not be <b>null</b>) * @param name the name of this configuration (can be <b>null</b>) * @param at the position of this configuration in the combined tree (can be * <b>null</b>) */
public void addConfiguration(final Configuration config, final String name, final String at) { if (config == null) { throw new IllegalArgumentException( "Added configuration must not be null!"); } beginWrite(true); try { if (name != null && namedConfigurations.containsKey(name)) { throw new ConfigurationRuntimeException( "A configuration with the name '" + name + "' already exists in this combined configuration!"); } final ConfigData cd = new ConfigData(config, name, at); if (getLogger().isDebugEnabled()) { getLogger() .debug("Adding configuration " + config + " with name " + name); } configurations.add(cd); if (name != null) { namedConfigurations.put(name, config); } invalidateInternal(); } finally { endWrite(); } registerListenerAt(config); }
Adds a new configuration to this combined configuration with an optional name. The new configuration's properties will be added under the root of the combined node structure.
Params:
  • config – the configuration to add (must not be null)
  • name – the name of this configuration (can be null)
/** * Adds a new configuration to this combined configuration with an optional * name. The new configuration's properties will be added under the root of * the combined node structure. * * @param config the configuration to add (must not be <b>null</b>) * @param name the name of this configuration (can be <b>null</b>) */
public void addConfiguration(final Configuration config, final String name) { addConfiguration(config, name, null); }
Adds a new configuration to this combined configuration. The new configuration is not given a name. Its properties will be added under the root of the combined node structure.
Params:
  • config – the configuration to add (must not be null)
/** * Adds a new configuration to this combined configuration. The new * configuration is not given a name. Its properties will be added under the * root of the combined node structure. * * @param config the configuration to add (must not be <b>null</b>) */
public void addConfiguration(final Configuration config) { addConfiguration(config, null, null); }
Returns the number of configurations that are contained in this combined configuration.
Returns:the number of contained configurations
/** * Returns the number of configurations that are contained in this combined * configuration. * * @return the number of contained configurations */
public int getNumberOfConfigurations() { beginRead(true); try { return getNumberOfConfigurationsInternal(); } finally { endRead(); } }
Returns the configuration at the specified index. The contained configurations are numbered in the order they were added to this combined configuration. The index of the first configuration is 0.
Params:
  • index – the index
Returns:the configuration at this index
/** * Returns the configuration at the specified index. The contained * configurations are numbered in the order they were added to this combined * configuration. The index of the first configuration is 0. * * @param index the index * @return the configuration at this index */
public Configuration getConfiguration(final int index) { beginRead(true); try { final ConfigData cd = configurations.get(index); return cd.getConfiguration(); } finally { endRead(); } }
Returns the configuration with the given name. This can be null if no such configuration exists.
Params:
  • name – the name of the configuration
Returns:the configuration with this name
/** * Returns the configuration with the given name. This can be <b>null</b> * if no such configuration exists. * * @param name the name of the configuration * @return the configuration with this name */
public Configuration getConfiguration(final String name) { beginRead(true); try { return namedConfigurations.get(name); } finally { endRead(); } }
Returns a List of all the configurations that have been added.
Returns:A List of all the configurations.
Since:1.7
/** * Returns a List of all the configurations that have been added. * @return A List of all the configurations. * @since 1.7 */
public List<Configuration> getConfigurations() { beginRead(true); try { final List<Configuration> list = new ArrayList<>(getNumberOfConfigurationsInternal()); for (final ConfigData cd : configurations) { list.add(cd.getConfiguration()); } return list; } finally { endRead(); } }
Returns a List of the names of all the configurations that have been added in the order they were added. A NULL value will be present in the list for each configuration that was added without a name.
Returns:A List of all the configuration names.
Since:1.7
/** * Returns a List of the names of all the configurations that have been * added in the order they were added. A NULL value will be present in * the list for each configuration that was added without a name. * @return A List of all the configuration names. * @since 1.7 */
public List<String> getConfigurationNameList() { beginRead(true); try { final List<String> list = new ArrayList<>(getNumberOfConfigurationsInternal()); for (final ConfigData cd : configurations) { list.add(cd.getName()); } return list; } finally { endRead(); } }
Removes the specified configuration from this combined configuration.
Params:
  • config – the configuration to be removed
Returns:a flag whether this configuration was found and could be removed
/** * Removes the specified configuration from this combined configuration. * * @param config the configuration to be removed * @return a flag whether this configuration was found and could be removed */
public boolean removeConfiguration(final Configuration config) { for (int index = 0; index < getNumberOfConfigurations(); index++) { if (configurations.get(index).getConfiguration() == config) { removeConfigurationAt(index); return true; } } return false; }
Removes the configuration at the specified index.
Params:
  • index – the index
Returns:the removed configuration
/** * Removes the configuration at the specified index. * * @param index the index * @return the removed configuration */
public Configuration removeConfigurationAt(final int index) { final ConfigData cd = configurations.remove(index); if (cd.getName() != null) { namedConfigurations.remove(cd.getName()); } unregisterListenerAt(cd.getConfiguration()); invalidateInternal(); return cd.getConfiguration(); }
Removes the configuration with the specified name.
Params:
  • name – the name of the configuration to be removed
Returns:the removed configuration (null if this configuration was not found)
/** * Removes the configuration with the specified name. * * @param name the name of the configuration to be removed * @return the removed configuration (<b>null</b> if this configuration * was not found) */
public Configuration removeConfiguration(final String name) { final Configuration conf = getConfiguration(name); if (conf != null) { removeConfiguration(conf); } return conf; }
Returns a set with the names of all configurations contained in this combined configuration. Of course here are only these configurations listed, for which a name was specified when they were added.
Returns:a set with the names of the contained configurations (never null)
/** * Returns a set with the names of all configurations contained in this * combined configuration. Of course here are only these configurations * listed, for which a name was specified when they were added. * * @return a set with the names of the contained configurations (never * <b>null</b>) */
public Set<String> getConfigurationNames() { beginRead(true); try { return namedConfigurations.keySet(); } finally { endRead(); } }
Invalidates this combined configuration. This means that the next time a property is accessed the combined node structure must be re-constructed. Invalidation of a combined configuration also means that an event of type EVENT_COMBINED_INVALIDATE is fired. Note that while other events most times appear twice (once before and once after an update), this event is only fired once (after update).
/** * Invalidates this combined configuration. This means that the next time a * property is accessed the combined node structure must be re-constructed. * Invalidation of a combined configuration also means that an event of type * {@code EVENT_COMBINED_INVALIDATE} is fired. Note that while other * events most times appear twice (once before and once after an update), * this event is only fired once (after update). */
public void invalidate() { beginWrite(true); try { invalidateInternal(); } finally { endWrite(); } }
Event listener call back for configuration update events. This method is called whenever one of the contained configurations was modified. It invalidates this combined configuration.
Params:
  • event – the update event
/** * Event listener call back for configuration update events. This method is * called whenever one of the contained configurations was modified. It * invalidates this combined configuration. * * @param event the update event */
@Override public void onEvent(final ConfigurationEvent event) { if (event.isBeforeUpdate()) { invalidate(); } }
Clears this configuration. All contained configurations will be removed.
/** * Clears this configuration. All contained configurations will be removed. */
@Override protected void clearInternal() { unregisterListenerAtChildren(); initChildCollections(); invalidateInternal(); }
Returns a copy of this object. This implementation performs a deep clone, i.e. all contained configurations will be cloned, too. For this to work, all contained configurations must be cloneable. Registered event listeners won't be cloned. The clone will use the same node combiner than the original.
Returns:the copied object
/** * Returns a copy of this object. This implementation performs a deep clone, * i.e. all contained configurations will be cloned, too. For this to work, * all contained configurations must be cloneable. Registered event * listeners won't be cloned. The clone will use the same node combiner than * the original. * * @return the copied object */
@Override public Object clone() { beginRead(false); try { final CombinedConfiguration copy = (CombinedConfiguration) super.clone(); copy.initChildCollections(); for (final ConfigData cd : configurations) { copy.addConfiguration(ConfigurationUtils.cloneConfiguration(cd .getConfiguration()), cd.getName(), cd.getAt()); } return copy; } finally { endRead(); } }
Returns the configuration source, in which the specified key is defined. This method will determine the configuration node that is identified by the given key. The following constellations are possible:
  • If no node object is found for this key, null is returned.
  • If the key maps to multiple nodes belonging to different configuration sources, a IllegalArgumentException is thrown (in this case no unique source can be determined).
  • If exactly one node is found for the key, the (child) configuration object, to which the node belongs is determined and returned.
  • For keys that have been added directly to this combined configuration and that do not belong to the namespaces defined by existing child configurations this configuration will be returned.
Params:
  • key – the key of a configuration property
Throws:
  • IllegalArgumentException – if the key maps to multiple properties and the source cannot be determined, or if the key is null
Returns:the configuration, to which this property belongs or null if the key cannot be resolved
Since:1.5
/** * Returns the configuration source, in which the specified key is defined. * This method will determine the configuration node that is identified by * the given key. The following constellations are possible: * <ul> * <li>If no node object is found for this key, <b>null</b> is returned.</li> * <li>If the key maps to multiple nodes belonging to different * configuration sources, a {@code IllegalArgumentException} is * thrown (in this case no unique source can be determined).</li> * <li>If exactly one node is found for the key, the (child) configuration * object, to which the node belongs is determined and returned.</li> * <li>For keys that have been added directly to this combined * configuration and that do not belong to the namespaces defined by * existing child configurations this configuration will be returned.</li> * </ul> * * @param key the key of a configuration property * @return the configuration, to which this property belongs or <b>null</b> * if the key cannot be resolved * @throws IllegalArgumentException if the key maps to multiple properties * and the source cannot be determined, or if the key is <b>null</b> * @since 1.5 */
public Configuration getSource(final String key) { if (key == null) { throw new IllegalArgumentException("Key must not be null!"); } final Set<Configuration> sources = getSources(key); if (sources.isEmpty()) { return null; } final Iterator<Configuration> iterator = sources.iterator(); final Configuration source = iterator.next(); if (iterator.hasNext()) { throw new IllegalArgumentException("The key " + key + " is defined by multiple sources!"); } return source; }
Returns a set with the configuration sources, in which the specified key is defined. This method determines the configuration nodes that are identified by the given key. It then determines the configuration sources to which these nodes belong and adds them to the result set. Note the following points:
  • If no node object is found for this key, an empty set is returned.
  • For keys that have been added directly to this combined configuration and that do not belong to the namespaces defined by existing child configurations this combined configuration is contained in the result set.
Params:
  • key – the key of a configuration property
Returns:a set with the configuration sources, which contain this property
Since:2.0
/** * Returns a set with the configuration sources, in which the specified key * is defined. This method determines the configuration nodes that are * identified by the given key. It then determines the configuration sources * to which these nodes belong and adds them to the result set. Note the * following points: * <ul> * <li>If no node object is found for this key, an empty set is returned.</li> * <li>For keys that have been added directly to this combined configuration * and that do not belong to the namespaces defined by existing child * configurations this combined configuration is contained in the result * set.</li> * </ul> * * @param key the key of a configuration property * @return a set with the configuration sources, which contain this property * @since 2.0 */
public Set<Configuration> getSources(final String key) { beginRead(false); try { final List<QueryResult<ImmutableNode>> results = fetchNodeList(key); final Set<Configuration> sources = new HashSet<>(); for (final QueryResult<ImmutableNode> result : results) { final Set<Configuration> resultSources = findSourceConfigurations(result.getNode()); if (resultSources.isEmpty()) { // key must be defined in combined configuration sources.add(this); } else { sources.addAll(resultSources); } } return sources; } finally { endRead(); } }
{@inheritDoc} This implementation checks whether a combined root node is available. If not, it is constructed by requesting a write lock.
/** * {@inheritDoc} This implementation checks whether a combined root node * is available. If not, it is constructed by requesting a write lock. */
@Override protected void beginRead(final boolean optimize) { if (optimize) { // just need a lock, don't construct configuration super.beginRead(true); return; } boolean lockObtained = false; do { super.beginRead(false); if (isUpToDate()) { lockObtained = true; } else { // release read lock and try to obtain a write lock endRead(); beginWrite(false); // this constructs the root node endWrite(); } } while (!lockObtained); }
{@inheritDoc} This implementation checks whether a combined root node is available. If not, it is constructed now.
/** * {@inheritDoc} This implementation checks whether a combined root node * is available. If not, it is constructed now. */
@Override protected void beginWrite(final boolean optimize) { super.beginWrite(true); if (optimize) { // just need a lock, don't construct configuration return; } try { if (!isUpToDate()) { getSubConfigurationParentModel().replaceRoot( constructCombinedNode(), this); upToDate = true; } } catch (final RuntimeException rex) { endWrite(); throw rex; } }
Returns a flag whether this configuration has been invalidated. This means that the combined nodes structure has to be rebuilt before the configuration can be accessed.
Returns:a flag whether this configuration is invalid
/** * Returns a flag whether this configuration has been invalidated. This * means that the combined nodes structure has to be rebuilt before the * configuration can be accessed. * * @return a flag whether this configuration is invalid */
private boolean isUpToDate() { return upToDate; }
Marks this configuration as invalid. This means that the next access re-creates the root node. An invalidate event is also fired. Note: This implementation expects that an exclusive (write) lock is held on this instance.
/** * Marks this configuration as invalid. This means that the next access * re-creates the root node. An invalidate event is also fired. Note: * This implementation expects that an exclusive (write) lock is held on * this instance. */
private void invalidateInternal() { upToDate = false; fireEvent(COMBINED_INVALIDATE, null, null, false); }
Initializes internal data structures for storing information about child configurations.
/** * Initializes internal data structures for storing information about * child configurations. */
private void initChildCollections() { configurations = new ArrayList<>(); namedConfigurations = new HashMap<>(); }
Creates the root node of this combined configuration.
Returns:the combined root node
/** * Creates the root node of this combined configuration. * * @return the combined root node */
private ImmutableNode constructCombinedNode() { if (getNumberOfConfigurationsInternal() < 1) { if (getLogger().isDebugEnabled()) { getLogger().debug("No configurations defined for " + this); } return EMPTY_ROOT; } final Iterator<ConfigData> it = configurations.iterator(); ImmutableNode node = it.next().getTransformedRoot(); while (it.hasNext()) { node = nodeCombiner.combine(node, it.next().getTransformedRoot()); } if (getLogger().isDebugEnabled()) { final ByteArrayOutputStream os = new ByteArrayOutputStream(); final PrintStream stream = new PrintStream(os); TreeUtils.printTree(stream, node); getLogger().debug(os.toString()); } return node; }
Determines the configurations to which the specified node belongs. This is done by inspecting the nodes structures of all child configurations.
Params:
  • node – the node
Returns:a set with the owning configurations
/** * Determines the configurations to which the specified node belongs. This * is done by inspecting the nodes structures of all child configurations. * * @param node the node * @return a set with the owning configurations */
private Set<Configuration> findSourceConfigurations(final ImmutableNode node) { final Set<Configuration> result = new HashSet<>(); final FindNodeVisitor<ImmutableNode> visitor = new FindNodeVisitor<>(node); for (final ConfigData cd : configurations) { NodeTreeWalker.INSTANCE.walkBFS(cd.getRootNode(), visitor, getModel().getNodeHandler()); if (visitor.isFound()) { result.add(cd.getConfiguration()); visitor.reset(); } } return result; }
Registers this combined configuration as listener at the given child configuration.
Params:
  • configuration – the child configuration
/** * Registers this combined configuration as listener at the given child * configuration. * * @param configuration the child configuration */
private void registerListenerAt(final Configuration configuration) { if (configuration instanceof EventSource) { ((EventSource) configuration).addEventListener( ConfigurationEvent.ANY, this); } }
Removes this combined configuration as listener from the given child configuration.
Params:
  • configuration – the child configuration
/** * Removes this combined configuration as listener from the given child * configuration. * * @param configuration the child configuration */
private void unregisterListenerAt(final Configuration configuration) { if (configuration instanceof EventSource) { ((EventSource) configuration).removeEventListener( ConfigurationEvent.ANY, this); } }
Removes this combined configuration as listener from all child configurations. This method is called on a clear() operation.
/** * Removes this combined configuration as listener from all child * configurations. This method is called on a clear() operation. */
private void unregisterListenerAtChildren() { if (configurations != null) { for (final ConfigData child : configurations) { unregisterListenerAt(child.getConfiguration()); } } }
Returns the number of child configurations in this combined configuration. The internal list of child configurations is accessed without synchronization.
Returns:the number of child configurations
/** * Returns the number of child configurations in this combined * configuration. The internal list of child configurations is accessed * without synchronization. * * @return the number of child configurations */
private int getNumberOfConfigurationsInternal() { return configurations.size(); }
An internal helper class for storing information about contained configurations.
/** * An internal helper class for storing information about contained * configurations. */
private class ConfigData {
Stores a reference to the configuration.
/** Stores a reference to the configuration. */
private final Configuration configuration;
Stores the name under which the configuration is stored.
/** Stores the name under which the configuration is stored. */
private final String name;
Stores the at information as path of nodes.
/** Stores the at information as path of nodes. */
private final Collection<String> atPath;
Stores the at string.
/** Stores the at string.*/
private final String at;
Stores the root node for this child configuration.
/** Stores the root node for this child configuration.*/
private ImmutableNode rootNode;
Creates a new instance of ConfigData and initializes it.
Params:
  • config – the configuration
  • n – the name
  • at – the at position
/** * Creates a new instance of {@code ConfigData} and initializes * it. * * @param config the configuration * @param n the name * @param at the at position */
public ConfigData(final Configuration config, final String n, final String at) { configuration = config; name = n; atPath = parseAt(at); this.at = at; }
Returns the stored configuration.
Returns:the configuration
/** * Returns the stored configuration. * * @return the configuration */
public Configuration getConfiguration() { return configuration; }
Returns the configuration's name.
Returns:the name
/** * Returns the configuration's name. * * @return the name */
public String getName() { return name; }
Returns the at position of this configuration.
Returns:the at position
/** * Returns the at position of this configuration. * * @return the at position */
public String getAt() { return at; }
Returns the root node for this child configuration.
Returns:the root node of this child configuration
Since:1.5
/** * Returns the root node for this child configuration. * * @return the root node of this child configuration * @since 1.5 */
public ImmutableNode getRootNode() { return rootNode; }
Returns the transformed root node of the stored configuration. The term "transformed" means that an eventually defined at path has been applied.
Returns:the transformed root node
/** * Returns the transformed root node of the stored configuration. The * term &quot;transformed&quot; means that an eventually defined at path * has been applied. * * @return the transformed root node */
public ImmutableNode getTransformedRoot() { final ImmutableNode configRoot = getRootNodeOfConfiguration(); return atPath == null ? configRoot : prependAtPath(configRoot); }
Prepends the at path to the given node.
Params:
  • node – the root node of the represented configuration
Returns:the new root node including the at path
/** * Prepends the at path to the given node. * * @param node the root node of the represented configuration * @return the new root node including the at path */
private ImmutableNode prependAtPath(final ImmutableNode node) { final ImmutableNode.Builder pathBuilder = new ImmutableNode.Builder(); final Iterator<String> pathIterator = atPath.iterator(); prependAtPathComponent(pathBuilder, pathIterator.next(), pathIterator, node); return new ImmutableNode.Builder(1).addChild(pathBuilder.create()) .create(); }
Handles a single component of the at path. A corresponding node is created and added to the hierarchical path to the original root node of the configuration.
Params:
  • builder – the current node builder object
  • currentComponent – the name of the current path component
  • components – an iterator with all components of the at path
  • orgRoot – the original root node of the wrapped configuration
/** * Handles a single component of the at path. A corresponding node is * created and added to the hierarchical path to the original root node * of the configuration. * * @param builder the current node builder object * @param currentComponent the name of the current path component * @param components an iterator with all components of the at path * @param orgRoot the original root node of the wrapped configuration */
private void prependAtPathComponent(final ImmutableNode.Builder builder, final String currentComponent, final Iterator<String> components, final ImmutableNode orgRoot) { builder.name(currentComponent); if (components.hasNext()) { final ImmutableNode.Builder childBuilder = new ImmutableNode.Builder(); prependAtPathComponent(childBuilder, components.next(), components, orgRoot); builder.addChild(childBuilder.create()); } else { builder.addChildren(orgRoot.getChildren()); builder.addAttributes(orgRoot.getAttributes()); builder.value(orgRoot.getValue()); } }
Obtains the root node of the wrapped configuration. If necessary, a hierarchical representation of the configuration has to be created first.
Returns:the root node of the associated configuration
/** * Obtains the root node of the wrapped configuration. If necessary, a * hierarchical representation of the configuration has to be created * first. * * @return the root node of the associated configuration */
private ImmutableNode getRootNodeOfConfiguration() { getConfiguration().lock(LockMode.READ); try { final ImmutableNode root = ConfigurationUtils .convertToHierarchical(getConfiguration(), conversionExpressionEngine).getNodeModel() .getInMemoryRepresentation(); rootNode = root; return root; } finally { getConfiguration().unlock(LockMode.READ); } }
Splits the at path into its components.
Params:
  • at – the at string
Returns:a collection with the names of the single components
/** * Splits the at path into its components. * * @param at the at string * @return a collection with the names of the single components */
private Collection<String> parseAt(final String at) { if (at == null) { return null; } final Collection<String> result = new ArrayList<>(); final DefaultConfigurationKey.KeyIterator it = new DefaultConfigurationKey( AT_ENGINE, at).iterator(); while (it.hasNext()) { result.add(it.nextKey()); } return result; } } }