CombinedConfiguration.java

  1. /*
  2.  * Licensed to the Apache Software Foundation (ASF) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * The ASF licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *     http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.apache.commons.configuration2;

  18. import java.io.ByteArrayOutputStream;
  19. import java.io.PrintStream;
  20. import java.util.ArrayList;
  21. import java.util.Collection;
  22. import java.util.HashMap;
  23. import java.util.HashSet;
  24. import java.util.Iterator;
  25. import java.util.List;
  26. import java.util.Map;
  27. import java.util.Set;
  28. import java.util.stream.Collectors;

  29. import org.apache.commons.configuration2.event.ConfigurationEvent;
  30. import org.apache.commons.configuration2.event.EventListener;
  31. import org.apache.commons.configuration2.event.EventSource;
  32. import org.apache.commons.configuration2.event.EventType;
  33. import org.apache.commons.configuration2.ex.ConfigurationRuntimeException;
  34. import org.apache.commons.configuration2.sync.LockMode;
  35. import org.apache.commons.configuration2.tree.DefaultConfigurationKey;
  36. import org.apache.commons.configuration2.tree.DefaultExpressionEngine;
  37. import org.apache.commons.configuration2.tree.ExpressionEngine;
  38. import org.apache.commons.configuration2.tree.ImmutableNode;
  39. import org.apache.commons.configuration2.tree.NodeCombiner;
  40. import org.apache.commons.configuration2.tree.NodeTreeWalker;
  41. import org.apache.commons.configuration2.tree.QueryResult;
  42. import org.apache.commons.configuration2.tree.TreeUtils;
  43. import org.apache.commons.configuration2.tree.UnionCombiner;
  44. import org.apache.commons.lang3.StringUtils;

  45. /**
  46.  * <p>
  47.  * A hierarchical composite configuration class.
  48.  * </p>
  49.  * <p>
  50.  * This class maintains a list of configuration objects, which can be added using the diverse {@code addConfiguration()}
  51.  * methods. After that the configurations can be accessed either by name (if one was provided when the configuration was
  52.  * added) or by index. For the whole set of managed configurations a logical node structure is constructed. For this
  53.  * purpose a {@link org.apache.commons.configuration2.tree.NodeCombiner NodeCombiner} object can be set. This makes it
  54.  * possible to specify different algorithms for the combination process.
  55.  * </p>
  56.  * <p>
  57.  * The big advantage of this class is that it creates a truly hierarchical structure of all the properties stored in the
  58.  * contained configurations - even if some of them are no hierarchical configurations per se. So all enhanced features
  59.  * provided by a hierarchical configuration (for example choosing an expression engine) are applicable.
  60.  * </p>
  61.  * <p>
  62.  * The class works by registering itself as an event listener at all added configurations. So it gets notified whenever
  63.  * one of these configurations is changed and can invalidate its internal node structure. The next time a property is
  64.  * accessed the node structure will be re-constructed using the current state of the managed configurations. Note that,
  65.  * depending on the used {@code NodeCombiner}, this may be a complex operation.
  66.  * </p>
  67.  * <p>
  68.  * Because of the way a {@code CombinedConfiguration} is working it has more or less view character: it provides a logic
  69.  * view on the configurations it contains. In this constellation not all methods defined for hierarchical configurations
  70.  * - especially methods that update the stored properties - can be implemented in a consistent manner. Using such
  71.  * methods (like {@code addProperty()}, or {@code clearProperty()} on a {@code CombinedConfiguration} is not strictly
  72.  * forbidden, however, depending on the current {@link NodeCombiner} and the involved properties, the results may be
  73.  * different than expected. Some examples may illustrate this:
  74.  * </p>
  75.  * <ul>
  76.  * <li>Imagine a {@code CombinedConfiguration} <em>cc</em> containing two child configurations with the following
  77.  * content:
  78.  * <dl>
  79.  * <dt>user.properties</dt>
  80.  * <dd>
  81.  *
  82.  * <pre>
  83.  * gui.background = blue
  84.  * gui.position = (10, 10, 400, 200)
  85.  * </pre>
  86.  *
  87.  * </dd>
  88.  * <dt>default.properties</dt>
  89.  * <dd>
  90.  *
  91.  * <pre>
  92.  * gui.background = black
  93.  * gui.foreground = white
  94.  * home.dir = /data
  95.  * </pre>
  96.  *
  97.  * </dd>
  98.  * </dl>
  99.  * As a {@code NodeCombiner} a {@link org.apache.commons.configuration2.tree.OverrideCombiner OverrideCombiner} is used.
  100.  * This combiner will ensure that defined user settings take precedence over the default values. If the resulting
  101.  * {@code CombinedConfiguration} is queried for the background color, {@code blue} will be returned because this value
  102.  * is defined in {@code user.properties}. Now consider what happens if the key {@code gui.background} is removed from
  103.  * the {@code CombinedConfiguration}:
  104.  *
  105.  * <pre>
  106.  * cc.clearProperty(&quot;gui.background&quot;);
  107.  * </pre>
  108.  *
  109.  * Will a {@code cc.containsKey("gui.background")} now return <strong>false</strong>? No, it won't! The {@code clearProperty()}
  110.  * operation is executed on the node set of the combined configuration, which was constructed from the nodes of the two
  111.  * child configurations. It causes the value of the <em>background</em> node to be cleared, which is also part of the
  112.  * first child configuration. This modification of one of its child configurations causes the
  113.  * {@code CombinedConfiguration} to be re-constructed. This time the {@code OverrideCombiner} cannot find a
  114.  * {@code gui.background} property in the first child configuration, but it finds one in the second, and adds it to the
  115.  * resulting combined configuration. So the property is still present (with a different value now).</li>
  116.  * <li>{@code addProperty()} can also be problematic: Most node combiners use special view nodes for linking parts of
  117.  * the original configurations' data together. If new properties are added to such a special node, they do not belong to
  118.  * any of the managed configurations and thus hang in the air. Using the same configurations as in the last example, the
  119.  * statement
  120.  *
  121.  * <pre>
  122.  * addProperty(&quot;database.user&quot;, &quot;scott&quot;);
  123.  * </pre>
  124.  *
  125.  * would cause such a hanging property. If now one of the child configurations is changed and the
  126.  * {@code CombinedConfiguration} is re-constructed, this property will disappear! (Add operations are not problematic if
  127.  * they result in a child configuration being updated. For instance an {@code addProperty("home.url", "localhost");}
  128.  * will alter the second child configuration - because the prefix <em>home</em> is here already present; when the
  129.  * {@code CombinedConfiguration} is re-constructed, this change is taken into account.)</li>
  130.  * </ul>
  131.  * <p>
  132.  * Because of such problems it is recommended to perform updates only on the managed child configurations.
  133.  * </p>
  134.  * <p>
  135.  * Whenever the node structure of a {@code CombinedConfiguration} becomes invalid (either because one of the contained
  136.  * configurations was modified or because the {@code invalidate()} method was directly called) an event is generated. So
  137.  * this can be detected by interested event listeners. This also makes it possible to add a combined configuration into
  138.  * another one.
  139.  * </p>
  140.  * <p>
  141.  * Notes about thread-safety: This configuration implementation uses a {@code Synchronizer} object to protect instances
  142.  * against concurrent access. The concrete {@code Synchronizer} implementation used determines whether an instance of
  143.  * this class is thread-safe or not. In contrast to other implementations derived from
  144.  * {@link BaseHierarchicalConfiguration}, thread-safety is an issue here because the nodes structure used by this
  145.  * configuration has to be constructed dynamically when a child configuration is changed. Therefore, when multiple
  146.  * threads are involved which also manipulate one of the child configurations, a proper {@code Synchronizer} object
  147.  * should be set. Note that the {@code Synchronizer} objects used by the child configurations do not really matter.
  148.  * Because immutable in-memory nodes structures are used for them there is no danger that updates on child
  149.  * configurations could interfere with read operations on the combined configuration.
  150.  * </p>
  151.  *
  152.  * @since 1.3
  153.  */
  154. public class CombinedConfiguration extends BaseHierarchicalConfiguration implements EventListener<ConfigurationEvent> {
  155.     /**
  156.      * An internal helper class for storing information about contained configurations.
  157.      */
  158.     private final class ConfigData {
  159.         /** Stores a reference to the configuration. */
  160.         private final Configuration configuration;

  161.         /** Stores the name under which the configuration is stored. */
  162.         private final String name;

  163.         /** Stores the at information as path of nodes. */
  164.         private final Collection<String> atPath;

  165.         /** Stores the at string. */
  166.         private final String at;

  167.         /** Stores the root node for this child configuration. */
  168.         private ImmutableNode rootNode;

  169.         /**
  170.          * Creates a new instance of {@code ConfigData} and initializes it.
  171.          *
  172.          * @param config the configuration
  173.          * @param n the name
  174.          * @param at the at position
  175.          */
  176.         public ConfigData(final Configuration config, final String n, final String at) {
  177.             configuration = config;
  178.             name = n;
  179.             atPath = parseAt(at);
  180.             this.at = at;
  181.         }

  182.         /**
  183.          * Gets the at position of this configuration.
  184.          *
  185.          * @return the at position
  186.          */
  187.         public String getAt() {
  188.             return at;
  189.         }

  190.         /**
  191.          * Gets the stored configuration.
  192.          *
  193.          * @return the configuration
  194.          */
  195.         public Configuration getConfiguration() {
  196.             return configuration;
  197.         }

  198.         /**
  199.          * Gets the configuration's name.
  200.          *
  201.          * @return the name
  202.          */
  203.         public String getName() {
  204.             return name;
  205.         }

  206.         /**
  207.          * Gets the root node for this child configuration.
  208.          *
  209.          * @return the root node of this child configuration
  210.          * @since 1.5
  211.          */
  212.         public ImmutableNode getRootNode() {
  213.             return rootNode;
  214.         }

  215.         /**
  216.          * Obtains the root node of the wrapped configuration. If necessary, a hierarchical representation of the configuration
  217.          * has to be created first.
  218.          *
  219.          * @return the root node of the associated configuration
  220.          */
  221.         private ImmutableNode getRootNodeOfConfiguration() {
  222.             getConfiguration().lock(LockMode.READ);
  223.             try {
  224.                 final ImmutableNode root = ConfigurationUtils.convertToHierarchical(getConfiguration(), conversionExpressionEngine).getNodeModel()
  225.                     .getInMemoryRepresentation();
  226.                 rootNode = root;
  227.                 return root;
  228.             } finally {
  229.                 getConfiguration().unlock(LockMode.READ);
  230.             }
  231.         }

  232.         /**
  233.          * Gets the transformed root node of the stored configuration. The term &quot;transformed&quot; means that an
  234.          * eventually defined at path has been applied.
  235.          *
  236.          * @return the transformed root node
  237.          */
  238.         public ImmutableNode getTransformedRoot() {
  239.             final ImmutableNode configRoot = getRootNodeOfConfiguration();
  240.             return atPath == null ? configRoot : prependAtPath(configRoot);
  241.         }

  242.         /**
  243.          * Splits the at path into its components.
  244.          *
  245.          * @param at the at string
  246.          * @return a collection with the names of the single components
  247.          */
  248.         private Collection<String> parseAt(final String at) {
  249.             if (StringUtils.isEmpty(at)) {
  250.                 return null;
  251.             }

  252.             final Collection<String> result = new ArrayList<>();
  253.             final DefaultConfigurationKey.KeyIterator it = new DefaultConfigurationKey(AT_ENGINE, at).iterator();
  254.             while (it.hasNext()) {
  255.                 result.add(it.nextKey());
  256.             }
  257.             return result;
  258.         }

  259.         /**
  260.          * Prepends the at path to the given node.
  261.          *
  262.          * @param node the root node of the represented configuration
  263.          * @return the new root node including the at path
  264.          */
  265.         private ImmutableNode prependAtPath(final ImmutableNode node) {
  266.             final ImmutableNode.Builder pathBuilder = new ImmutableNode.Builder();
  267.             final Iterator<String> pathIterator = atPath.iterator();
  268.             prependAtPathComponent(pathBuilder, pathIterator.next(), pathIterator, node);
  269.             return new ImmutableNode.Builder(1).addChild(pathBuilder.create()).create();
  270.         }

  271.         /**
  272.          * Handles a single component of the at path. A corresponding node is created and added to the hierarchical path to the
  273.          * original root node of the configuration.
  274.          *
  275.          * @param builder the current node builder object
  276.          * @param currentComponent the name of the current path component
  277.          * @param components an iterator with all components of the at path
  278.          * @param orgRoot the original root node of the wrapped configuration
  279.          */
  280.         private void prependAtPathComponent(final ImmutableNode.Builder builder, final String currentComponent, final Iterator<String> components,
  281.             final ImmutableNode orgRoot) {
  282.             builder.name(currentComponent);
  283.             if (components.hasNext()) {
  284.                 final ImmutableNode.Builder childBuilder = new ImmutableNode.Builder();
  285.                 prependAtPathComponent(childBuilder, components.next(), components, orgRoot);
  286.                 builder.addChild(childBuilder.create());
  287.             } else {
  288.                 builder.addChildren(orgRoot.getChildren());
  289.                 builder.addAttributes(orgRoot.getAttributes());
  290.                 builder.value(orgRoot.getValue());
  291.             }
  292.         }
  293.     }

  294.     /**
  295.      * Constant for the event type fired when the internal node structure of a combined configuration becomes invalid.
  296.      *
  297.      * @since 2.0
  298.      */
  299.     public static final EventType<ConfigurationEvent> COMBINED_INVALIDATE = new EventType<>(ConfigurationEvent.ANY, "COMBINED_INVALIDATE");

  300.     /** Constant for the expression engine for parsing the at path. */
  301.     private static final DefaultExpressionEngine AT_ENGINE = DefaultExpressionEngine.INSTANCE;

  302.     /** Constant for the default node combiner. */
  303.     private static final NodeCombiner DEFAULT_COMBINER = new UnionCombiner();

  304.     /** Constant for a root node for an empty configuration. */
  305.     private static final ImmutableNode EMPTY_ROOT = new ImmutableNode.Builder().create();

  306.     /** Stores the combiner. */
  307.     private NodeCombiner nodeCombiner;

  308.     /** Stores a list with the contained configurations. */
  309.     private List<ConfigData> configurations;

  310.     /** Stores a map with the named configurations. */
  311.     private Map<String, Configuration> namedConfigurations;

  312.     /**
  313.      * An expression engine used for converting child configurations to hierarchical ones.
  314.      */
  315.     private ExpressionEngine conversionExpressionEngine;

  316.     /** A flag whether this configuration is up-to-date. */
  317.     private boolean upToDate;

  318.     /**
  319.      * Creates a new instance of {@code CombinedConfiguration} that uses a union combiner.
  320.      *
  321.      * @see org.apache.commons.configuration2.tree.UnionCombiner
  322.      */
  323.     public CombinedConfiguration() {
  324.         this(null);
  325.     }

  326.     /**
  327.      * Creates a new instance of {@code CombinedConfiguration} and initializes the combiner to be used.
  328.      *
  329.      * @param comb the node combiner (can be <strong>null</strong>, then a union combiner is used as default)
  330.      */
  331.     public CombinedConfiguration(final NodeCombiner comb) {
  332.         nodeCombiner = comb != null ? comb : DEFAULT_COMBINER;
  333.         initChildCollections();
  334.     }

  335.     /**
  336.      * Adds a new configuration to this combined configuration. The new configuration is not given a name. Its properties
  337.      * will be added under the root of the combined node structure.
  338.      *
  339.      * @param config the configuration to add (must not be <strong>null</strong>)
  340.      */
  341.     public void addConfiguration(final Configuration config) {
  342.         addConfiguration(config, null, null);
  343.     }

  344.     /**
  345.      * Adds a new configuration to this combined configuration with an optional name. The new configuration's properties
  346.      * will be added under the root of the combined node structure.
  347.      *
  348.      * @param config the configuration to add (must not be <strong>null</strong>)
  349.      * @param name the name of this configuration (can be <strong>null</strong>)
  350.      */
  351.     public void addConfiguration(final Configuration config, final String name) {
  352.         addConfiguration(config, name, null);
  353.     }

  354.     /**
  355.      * Adds a new configuration to this combined configuration. It is possible (but not mandatory) to give the new
  356.      * configuration a name. This name must be unique, otherwise a {@code ConfigurationRuntimeException} will be thrown.
  357.      * With the optional {@code at} argument you can specify where in the resulting node structure the content of the added
  358.      * configuration should appear. This is a string that uses dots as property delimiters (independent on the current
  359.      * expression engine). For instance if you pass in the string {@code "database.tables"}, all properties of the added
  360.      * configuration will occur in this branch.
  361.      *
  362.      * @param config the configuration to add (must not be <strong>null</strong>)
  363.      * @param name the name of this configuration (can be <strong>null</strong>)
  364.      * @param at the position of this configuration in the combined tree (can be <strong>null</strong>)
  365.      */
  366.     public void addConfiguration(final Configuration config, final String name, final String at) {
  367.         if (config == null) {
  368.             throw new IllegalArgumentException("Added configuration must not be null!");
  369.         }

  370.         beginWrite(true);
  371.         try {
  372.             if (name != null && namedConfigurations.containsKey(name)) {
  373.                 throw new ConfigurationRuntimeException("A configuration with the name '" + name + "' already exists in this combined configuration!");
  374.             }

  375.             final ConfigData cd = new ConfigData(config, name, at);
  376.             if (getLogger().isDebugEnabled()) {
  377.                 getLogger().debug("Adding configuration " + config + " with name " + name);
  378.             }
  379.             configurations.add(cd);
  380.             if (name != null) {
  381.                 namedConfigurations.put(name, config);
  382.             }

  383.             invalidateInternal();
  384.         } finally {
  385.             endWrite();
  386.         }
  387.         registerListenerAt(config);
  388.     }

  389.     /**
  390.      * {@inheritDoc} This implementation checks whether a combined root node is available. If not, it is constructed by
  391.      * requesting a write lock.
  392.      */
  393.     @Override
  394.     protected void beginRead(final boolean optimize) {
  395.         if (optimize) {
  396.             // just need a lock, don't construct configuration
  397.             super.beginRead(true);
  398.             return;
  399.         }

  400.         boolean lockObtained = false;
  401.         do {
  402.             super.beginRead(false);
  403.             if (isUpToDate()) {
  404.                 lockObtained = true;
  405.             } else {
  406.                 // release read lock and try to obtain a write lock
  407.                 endRead();
  408.                 beginWrite(false); // this constructs the root node
  409.                 endWrite();
  410.             }
  411.         } while (!lockObtained);
  412.     }

  413.     /**
  414.      * {@inheritDoc} This implementation checks whether a combined root node is available. If not, it is constructed now.
  415.      */
  416.     @Override
  417.     protected void beginWrite(final boolean optimize) {
  418.         super.beginWrite(true);
  419.         if (optimize) {
  420.             // just need a lock, don't construct configuration
  421.             return;
  422.         }

  423.         boolean success = false;
  424.         try {
  425.             if (!isUpToDate()) {
  426.                 getSubConfigurationParentModel().replaceRoot(constructCombinedNode(), this);
  427.                 upToDate = true;
  428.             }
  429.             success = true;
  430.         } finally {
  431.             if (!success) {
  432.                 endWrite();
  433.             }
  434.         }
  435.     }

  436.     /**
  437.      * Clears this configuration. All contained configurations will be removed.
  438.      */
  439.     @Override
  440.     protected void clearInternal() {
  441.         unregisterListenerAtChildren();
  442.         initChildCollections();
  443.         invalidateInternal();
  444.     }

  445.     /**
  446.      * Returns a copy of this object. This implementation performs a deep clone, i.e. all contained configurations will be
  447.      * cloned, too. For this to work, all contained configurations must be cloneable. Registered event listeners won't be
  448.      * cloned. The clone will use the same node combiner than the original.
  449.      *
  450.      * @return the copied object
  451.      */
  452.     @Override
  453.     public Object clone() {
  454.         beginRead(false);
  455.         try {
  456.             final CombinedConfiguration copy = (CombinedConfiguration) super.clone();
  457.             copy.initChildCollections();
  458.             configurations.forEach(cd -> copy.addConfiguration(ConfigurationUtils.cloneConfiguration(cd.getConfiguration()), cd.getName(), cd.getAt()));

  459.             return copy;
  460.         } finally {
  461.             endRead();
  462.         }
  463.     }

  464.     /**
  465.      * Creates the root node of this combined configuration.
  466.      *
  467.      * @return the combined root node
  468.      */
  469.     private ImmutableNode constructCombinedNode() {
  470.         if (getNumberOfConfigurationsInternal() < 1) {
  471.             if (getLogger().isDebugEnabled()) {
  472.                 getLogger().debug("No configurations defined for " + this);
  473.             }
  474.             return EMPTY_ROOT;
  475.         }
  476.         final Iterator<ConfigData> it = configurations.iterator();
  477.         ImmutableNode node = it.next().getTransformedRoot();
  478.         while (it.hasNext()) {
  479.             node = nodeCombiner.combine(node, it.next().getTransformedRoot());
  480.         }
  481.         if (getLogger().isDebugEnabled()) {
  482.             final ByteArrayOutputStream os = new ByteArrayOutputStream();
  483.             final PrintStream stream = new PrintStream(os);
  484.             TreeUtils.printTree(stream, node);
  485.             getLogger().debug(os.toString());
  486.         }
  487.         return node;
  488.     }

  489.     /**
  490.      * Determines the configurations to which the specified node belongs. This is done by inspecting the nodes structures of
  491.      * all child configurations.
  492.      *
  493.      * @param node the node
  494.      * @return a set with the owning configurations
  495.      */
  496.     private Set<Configuration> findSourceConfigurations(final ImmutableNode node) {
  497.         final Set<Configuration> result = new HashSet<>();
  498.         final FindNodeVisitor<ImmutableNode> visitor = new FindNodeVisitor<>(node);

  499.         configurations.forEach(cd -> {
  500.             NodeTreeWalker.INSTANCE.walkBFS(cd.getRootNode(), visitor, getModel().getNodeHandler());
  501.             if (visitor.isFound()) {
  502.                 result.add(cd.getConfiguration());
  503.                 visitor.reset();
  504.             }
  505.         });

  506.         return result;
  507.     }

  508.     /**
  509.      * Gets the configuration at the specified index. The contained configurations are numbered in the order they were
  510.      * added to this combined configuration. The index of the first configuration is 0.
  511.      *
  512.      * @param index the index
  513.      * @return the configuration at this index
  514.      */
  515.     public Configuration getConfiguration(final int index) {
  516.         beginRead(true);
  517.         try {
  518.             final ConfigData cd = configurations.get(index);
  519.             return cd.getConfiguration();
  520.         } finally {
  521.             endRead();
  522.         }
  523.     }

  524.     /**
  525.      * Gets the configuration with the given name. This can be <strong>null</strong> if no such configuration exists.
  526.      *
  527.      * @param name the name of the configuration
  528.      * @return the configuration with this name
  529.      */
  530.     public Configuration getConfiguration(final String name) {
  531.         beginRead(true);
  532.         try {
  533.             return namedConfigurations.get(name);
  534.         } finally {
  535.             endRead();
  536.         }
  537.     }

  538.     /**
  539.      * Gets a List of the names of all the configurations that have been added in the order they were added. A NULL value
  540.      * will be present in the list for each configuration that was added without a name.
  541.      *
  542.      * @return A List of all the configuration names.
  543.      * @since 1.7
  544.      */
  545.     public List<String> getConfigurationNameList() {
  546.         beginRead(true);
  547.         try {
  548.             return configurations.stream().map(ConfigData::getName).collect(Collectors.toList());
  549.         } finally {
  550.             endRead();
  551.         }
  552.     }

  553.     /**
  554.      * Gets a set with the names of all configurations contained in this combined configuration. Of course here are only
  555.      * these configurations listed, for which a name was specified when they were added.
  556.      *
  557.      * @return a set with the names of the contained configurations (never <strong>null</strong>)
  558.      */
  559.     public Set<String> getConfigurationNames() {
  560.         beginRead(true);
  561.         try {
  562.             return namedConfigurations.keySet();
  563.         } finally {
  564.             endRead();
  565.         }
  566.     }

  567.     /**
  568.      * Gets a List of all the configurations that have been added.
  569.      *
  570.      * @return A List of all the configurations.
  571.      * @since 1.7
  572.      */
  573.     public List<Configuration> getConfigurations() {
  574.         beginRead(true);
  575.         try {
  576.             return configurations.stream().map(ConfigData::getConfiguration).collect(Collectors.toList());
  577.         } finally {
  578.             endRead();
  579.         }
  580.     }

  581.     /**
  582.      * Gets the {@code ExpressionEngine} for converting flat child configurations to hierarchical ones.
  583.      *
  584.      * @return the conversion expression engine
  585.      * @since 1.6
  586.      */
  587.     public ExpressionEngine getConversionExpressionEngine() {
  588.         beginRead(true);
  589.         try {
  590.             return conversionExpressionEngine;
  591.         } finally {
  592.             endRead();
  593.         }
  594.     }

  595.     /**
  596.      * Gets the node combiner that is used for creating the combined node structure.
  597.      *
  598.      * @return the node combiner
  599.      */
  600.     public NodeCombiner getNodeCombiner() {
  601.         beginRead(true);
  602.         try {
  603.             return nodeCombiner;
  604.         } finally {
  605.             endRead();
  606.         }
  607.     }

  608.     /**
  609.      * Gets the number of configurations that are contained in this combined configuration.
  610.      *
  611.      * @return the number of contained configurations
  612.      */
  613.     public int getNumberOfConfigurations() {
  614.         beginRead(true);
  615.         try {
  616.             return getNumberOfConfigurationsInternal();
  617.         } finally {
  618.             endRead();
  619.         }
  620.     }

  621.     /**
  622.      * Gets the number of child configurations in this combined configuration. The internal list of child configurations
  623.      * is accessed without synchronization.
  624.      *
  625.      * @return the number of child configurations
  626.      */
  627.     private int getNumberOfConfigurationsInternal() {
  628.         return configurations.size();
  629.     }

  630.     /**
  631.      * Gets the configuration source, in which the specified key is defined. This method will determine the configuration
  632.      * node that is identified by the given key. The following constellations are possible:
  633.      * <ul>
  634.      * <li>If no node object is found for this key, <strong>null</strong> is returned.</li>
  635.      * <li>If the key maps to multiple nodes belonging to different configuration sources, a
  636.      * {@code IllegalArgumentException} is thrown (in this case no unique source can be determined).</li>
  637.      * <li>If exactly one node is found for the key, the (child) configuration object, to which the node belongs is
  638.      * determined and returned.</li>
  639.      * <li>For keys that have been added directly to this combined configuration and that do not belong to the namespaces
  640.      * defined by existing child configurations this configuration will be returned.</li>
  641.      * </ul>
  642.      *
  643.      * @param key the key of a configuration property
  644.      * @return the configuration, to which this property belongs or <strong>null</strong> if the key cannot be resolved
  645.      * @throws IllegalArgumentException if the key maps to multiple properties and the source cannot be determined, or if
  646.      *         the key is <strong>null</strong>
  647.      * @since 1.5
  648.      */
  649.     public Configuration getSource(final String key) {
  650.         if (key == null) {
  651.             throw new IllegalArgumentException("Key must not be null!");
  652.         }

  653.         final Set<Configuration> sources = getSources(key);
  654.         if (sources.isEmpty()) {
  655.             return null;
  656.         }
  657.         final Iterator<Configuration> iterator = sources.iterator();
  658.         final Configuration source = iterator.next();
  659.         if (iterator.hasNext()) {
  660.             throw new IllegalArgumentException("The key " + key + " is defined by multiple sources!");
  661.         }
  662.         return source;
  663.     }

  664.     /**
  665.      * Gets a set with the configuration sources, in which the specified key is defined. This method determines the
  666.      * configuration nodes that are identified by the given key. It then determines the configuration sources to which these
  667.      * nodes belong and adds them to the result set. Note the following points:
  668.      * <ul>
  669.      * <li>If no node object is found for this key, an empty set is returned.</li>
  670.      * <li>For keys that have been added directly to this combined configuration and that do not belong to the namespaces
  671.      * defined by existing child configurations this combined configuration is contained in the result set.</li>
  672.      * </ul>
  673.      *
  674.      * @param key the key of a configuration property
  675.      * @return a set with the configuration sources, which contain this property
  676.      * @since 2.0
  677.      */
  678.     public Set<Configuration> getSources(final String key) {
  679.         beginRead(false);
  680.         try {
  681.             final List<QueryResult<ImmutableNode>> results = fetchNodeList(key);
  682.             final Set<Configuration> sources = new HashSet<>();

  683.             results.forEach(result -> {
  684.                 final Set<Configuration> resultSources = findSourceConfigurations(result.getNode());
  685.                 if (resultSources.isEmpty()) {
  686.                     // key must be defined in combined configuration
  687.                     sources.add(this);
  688.                 } else {
  689.                     sources.addAll(resultSources);
  690.                 }
  691.             });

  692.             return sources;
  693.         } finally {
  694.             endRead();
  695.         }
  696.     }

  697.     /**
  698.      * Initializes internal data structures for storing information about child configurations.
  699.      */
  700.     private void initChildCollections() {
  701.         configurations = new ArrayList<>();
  702.         namedConfigurations = new HashMap<>();
  703.     }

  704.     /**
  705.      * Invalidates this combined configuration. This means that the next time a property is accessed the combined node
  706.      * structure must be re-constructed. Invalidation of a combined configuration also means that an event of type
  707.      * {@code EVENT_COMBINED_INVALIDATE} is fired. Note that while other events most times appear twice (once before and
  708.      * once after an update), this event is only fired once (after update).
  709.      */
  710.     public void invalidate() {
  711.         beginWrite(true);
  712.         try {
  713.             invalidateInternal();
  714.         } finally {
  715.             endWrite();
  716.         }
  717.     }

  718.     /**
  719.      * Marks this configuration as invalid. This means that the next access re-creates the root node. An invalidate event is
  720.      * also fired. Note: This implementation expects that an exclusive (write) lock is held on this instance.
  721.      */
  722.     private void invalidateInternal() {
  723.         upToDate = false;
  724.         fireEvent(COMBINED_INVALIDATE, null, null, false);
  725.     }

  726.     /**
  727.      * Returns a flag whether this configuration has been invalidated. This means that the combined nodes structure has to
  728.      * be rebuilt before the configuration can be accessed.
  729.      *
  730.      * @return a flag whether this configuration is invalid
  731.      */
  732.     private boolean isUpToDate() {
  733.         return upToDate;
  734.     }

  735.     /**
  736.      * Event listener call back for configuration update events. This method is called whenever one of the contained
  737.      * configurations was modified. It invalidates this combined configuration.
  738.      *
  739.      * @param event the update event
  740.      */
  741.     @Override
  742.     public void onEvent(final ConfigurationEvent event) {
  743.         if (event.isBeforeUpdate()) {
  744.             invalidate();
  745.         }
  746.     }

  747.     /**
  748.      * Registers this combined configuration as listener at the given child configuration.
  749.      *
  750.      * @param configuration the child configuration
  751.      */
  752.     private void registerListenerAt(final Configuration configuration) {
  753.         if (configuration instanceof EventSource) {
  754.             ((EventSource) configuration).addEventListener(ConfigurationEvent.ANY, this);
  755.         }
  756.     }

  757.     /**
  758.      * Removes the specified configuration from this combined configuration.
  759.      *
  760.      * @param config the configuration to be removed
  761.      * @return a flag whether this configuration was found and could be removed
  762.      */
  763.     public boolean removeConfiguration(final Configuration config) {
  764.         for (int index = 0; index < getNumberOfConfigurations(); index++) {
  765.             if (configurations.get(index).getConfiguration() == config) {
  766.                 removeConfigurationAt(index);
  767.                 return true;
  768.             }
  769.         }

  770.         return false;
  771.     }

  772.     /**
  773.      * Removes the configuration with the specified name.
  774.      *
  775.      * @param name the name of the configuration to be removed
  776.      * @return the removed configuration (<strong>null</strong> if this configuration was not found)
  777.      */
  778.     public Configuration removeConfiguration(final String name) {
  779.         final Configuration conf = getConfiguration(name);
  780.         if (conf != null) {
  781.             removeConfiguration(conf);
  782.         }
  783.         return conf;
  784.     }

  785.     /**
  786.      * Removes the configuration at the specified index.
  787.      *
  788.      * @param index the index
  789.      * @return the removed configuration
  790.      */
  791.     public Configuration removeConfigurationAt(final int index) {
  792.         final ConfigData cd = configurations.remove(index);
  793.         if (cd.getName() != null) {
  794.             namedConfigurations.remove(cd.getName());
  795.         }
  796.         unregisterListenerAt(cd.getConfiguration());
  797.         invalidateInternal();
  798.         return cd.getConfiguration();
  799.     }

  800.     /**
  801.      * Sets the {@code ExpressionEngine} for converting flat child configurations to hierarchical ones. When constructing
  802.      * the root node for this combined configuration the properties of all child configurations must be combined to a single
  803.      * hierarchical node structure. In this process, non hierarchical configurations are converted to hierarchical ones
  804.      * first. This can be problematic if a child configuration contains keys that are no compatible with the default
  805.      * expression engine used by hierarchical configurations. Therefore it is possible to specify a specific expression
  806.      * engine to be used for this purpose.
  807.      *
  808.      * @param conversionExpressionEngine the conversion expression engine
  809.      * @see ConfigurationUtils#convertToHierarchical(Configuration, ExpressionEngine)
  810.      * @since 1.6
  811.      */
  812.     public void setConversionExpressionEngine(final ExpressionEngine conversionExpressionEngine) {
  813.         beginWrite(true);
  814.         try {
  815.             this.conversionExpressionEngine = conversionExpressionEngine;
  816.         } finally {
  817.             endWrite();
  818.         }
  819.     }

  820.     /**
  821.      * Sets the node combiner. This object will be used when the combined node structure is to be constructed. It must not
  822.      * be <strong>null</strong>, otherwise an {@code IllegalArgumentException} exception is thrown. Changing the node combiner causes
  823.      * an invalidation of this combined configuration, so that the new combiner immediately takes effect.
  824.      *
  825.      * @param nodeCombiner the node combiner
  826.      */
  827.     public void setNodeCombiner(final NodeCombiner nodeCombiner) {
  828.         if (nodeCombiner == null) {
  829.             throw new IllegalArgumentException("Node combiner must not be null!");
  830.         }

  831.         beginWrite(true);
  832.         try {
  833.             this.nodeCombiner = nodeCombiner;
  834.             invalidateInternal();
  835.         } finally {
  836.             endWrite();
  837.         }
  838.     }

  839.     /**
  840.      * Removes this combined configuration as listener from the given child configuration.
  841.      *
  842.      * @param configuration the child configuration
  843.      */
  844.     private void unregisterListenerAt(final Configuration configuration) {
  845.         if (configuration instanceof EventSource) {
  846.             ((EventSource) configuration).removeEventListener(ConfigurationEvent.ANY, this);
  847.         }
  848.     }

  849.     /**
  850.      * Removes this combined configuration as listener from all child configurations. This method is called on a clear()
  851.      * operation.
  852.      */
  853.     private void unregisterListenerAtChildren() {
  854.         if (configurations != null) {
  855.             configurations.forEach(child -> unregisterListenerAt(child.getConfiguration()));
  856.         }
  857.     }
  858. }