CombinedConfiguration.java
- /*
- * 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 java.util.stream.Collectors;
- 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;
- import org.apache.commons.lang3.StringUtils;
- /**
- * <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 (for example 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("gui.background");
- * </pre>
- *
- * Will a {@code cc.containsKey("gui.background")} now return <strong>false</strong>? 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("database.user", "scott");
- * </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> {
- /**
- * An internal helper class for storing information about contained configurations.
- */
- private final class ConfigData {
- /** Stores a reference to the configuration. */
- private final Configuration configuration;
- /** Stores the name under which the configuration is stored. */
- private final String name;
- /** Stores the at information as path of nodes. */
- private final Collection<String> atPath;
- /** Stores the at string. */
- private final String at;
- /** Stores the root node for this child configuration. */
- private ImmutableNode rootNode;
- /**
- * 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;
- }
- /**
- * Gets the at position of this configuration.
- *
- * @return the at position
- */
- public String getAt() {
- return at;
- }
- /**
- * Gets the stored configuration.
- *
- * @return the configuration
- */
- public Configuration getConfiguration() {
- return configuration;
- }
- /**
- * Gets the configuration's name.
- *
- * @return the name
- */
- public String getName() {
- return name;
- }
- /**
- * Gets the root node for this child configuration.
- *
- * @return the root node of this child configuration
- * @since 1.5
- */
- public ImmutableNode getRootNode() {
- return rootNode;
- }
- /**
- * 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);
- }
- }
- /**
- * Gets the transformed root node of the stored configuration. The term "transformed" 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);
- }
- /**
- * 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 (StringUtils.isEmpty(at)) {
- 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;
- }
- /**
- * 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.
- *
- * @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());
- }
- }
- }
- /**
- * 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. */
- private static final DefaultExpressionEngine AT_ENGINE = DefaultExpressionEngine.INSTANCE;
- /** Constant for the default node combiner. */
- private static final NodeCombiner DEFAULT_COMBINER = new UnionCombiner();
- /** Constant for a root node for an empty configuration. */
- private static final ImmutableNode EMPTY_ROOT = new ImmutableNode.Builder().create();
- /** Stores the combiner. */
- private NodeCombiner nodeCombiner;
- /** Stores a list with the contained configurations. */
- private List<ConfigData> configurations;
- /** Stores a map with the named configurations. */
- private Map<String, Configuration> namedConfigurations;
- /**
- * An expression engine used for converting child configurations to hierarchical ones.
- */
- private ExpressionEngine conversionExpressionEngine;
- /** A flag whether this configuration is up-to-date. */
- private boolean upToDate;
- /**
- * Creates a new instance of {@code CombinedConfiguration} that uses a union combiner.
- *
- * @see org.apache.commons.configuration2.tree.UnionCombiner
- */
- public CombinedConfiguration() {
- this(null);
- }
- /**
- * Creates a new instance of {@code CombinedConfiguration} and initializes the combiner to be used.
- *
- * @param comb the node combiner (can be <strong>null</strong>, then a union combiner is used as default)
- */
- public CombinedConfiguration(final NodeCombiner comb) {
- nodeCombiner = comb != null ? comb : DEFAULT_COMBINER;
- initChildCollections();
- }
- /**
- * 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 <strong>null</strong>)
- */
- public void addConfiguration(final Configuration config) {
- addConfiguration(config, null, 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 <strong>null</strong>)
- * @param name the name of this configuration (can be <strong>null</strong>)
- */
- public void addConfiguration(final Configuration config, final String name) {
- addConfiguration(config, name, 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 <strong>null</strong>)
- * @param name the name of this configuration (can be <strong>null</strong>)
- * @param at the position of this configuration in the combined tree (can be <strong>null</strong>)
- */
- 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);
- }
- /**
- * {@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.
- */
- @Override
- protected void beginWrite(final boolean optimize) {
- super.beginWrite(true);
- if (optimize) {
- // just need a lock, don't construct configuration
- return;
- }
- boolean success = false;
- try {
- if (!isUpToDate()) {
- getSubConfigurationParentModel().replaceRoot(constructCombinedNode(), this);
- upToDate = true;
- }
- success = true;
- } finally {
- if (!success) {
- endWrite();
- }
- }
- }
- /**
- * 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.
- *
- * @return the copied object
- */
- @Override
- public Object clone() {
- beginRead(false);
- try {
- final CombinedConfiguration copy = (CombinedConfiguration) super.clone();
- copy.initChildCollections();
- configurations.forEach(cd -> copy.addConfiguration(ConfigurationUtils.cloneConfiguration(cd.getConfiguration()), cd.getName(), cd.getAt()));
- return copy;
- } finally {
- endRead();
- }
- }
- /**
- * 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.
- *
- * @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);
- configurations.forEach(cd -> {
- NodeTreeWalker.INSTANCE.walkBFS(cd.getRootNode(), visitor, getModel().getNodeHandler());
- if (visitor.isFound()) {
- result.add(cd.getConfiguration());
- visitor.reset();
- }
- });
- return result;
- }
- /**
- * Gets 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();
- }
- }
- /**
- * Gets the configuration with the given name. This can be <strong>null</strong> 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();
- }
- }
- /**
- * Gets 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 {
- return configurations.stream().map(ConfigData::getName).collect(Collectors.toList());
- } finally {
- endRead();
- }
- }
- /**
- * Gets 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 <strong>null</strong>)
- */
- public Set<String> getConfigurationNames() {
- beginRead(true);
- try {
- return namedConfigurations.keySet();
- } finally {
- endRead();
- }
- }
- /**
- * Gets 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 {
- return configurations.stream().map(ConfigData::getConfiguration).collect(Collectors.toList());
- } finally {
- endRead();
- }
- }
- /**
- * Gets 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();
- }
- }
- /**
- * Gets 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();
- }
- }
- /**
- * Gets 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();
- }
- }
- /**
- * Gets 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();
- }
- /**
- * Gets 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, <strong>null</strong> 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 <strong>null</strong> 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 <strong>null</strong>
- * @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;
- }
- /**
- * Gets 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<>();
- results.forEach(result -> {
- 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();
- }
- }
- /**
- * Initializes internal data structures for storing information about child configurations.
- */
- private void initChildCollections() {
- configurations = new ArrayList<>();
- namedConfigurations = new HashMap<>();
- }
- /**
- * 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();
- }
- }
- /**
- * 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);
- }
- /**
- * 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;
- }
- /**
- * 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();
- }
- }
- /**
- * 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 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 with the specified name.
- *
- * @param name the name of the configuration to be removed
- * @return the removed configuration (<strong>null</strong> if this configuration was not found)
- */
- public Configuration removeConfiguration(final String name) {
- final Configuration conf = getConfiguration(name);
- if (conf != null) {
- removeConfiguration(conf);
- }
- return conf;
- }
- /**
- * 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();
- }
- /**
- * 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();
- }
- }
- /**
- * Sets the node combiner. This object will be used when the combined node structure is to be constructed. It must not
- * be <strong>null</strong>, 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();
- }
- }
- /**
- * 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.
- */
- private void unregisterListenerAtChildren() {
- if (configurations != null) {
- configurations.forEach(child -> unregisterListenerAt(child.getConfiguration()));
- }
- }
- }