DynamicCombinedConfiguration.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.math.BigDecimal;
  19. import java.math.BigInteger;
  20. import java.util.ArrayList;
  21. import java.util.Collection;
  22. import java.util.Collections;
  23. import java.util.HashMap;
  24. import java.util.Iterator;
  25. import java.util.List;
  26. import java.util.Map;
  27. import java.util.Properties;
  28. import java.util.Set;
  29. import java.util.concurrent.ConcurrentHashMap;
  30. import java.util.concurrent.ConcurrentMap;

  31. import org.apache.commons.configuration2.event.BaseEventSource;
  32. import org.apache.commons.configuration2.event.Event;
  33. import org.apache.commons.configuration2.event.EventListener;
  34. import org.apache.commons.configuration2.event.EventType;
  35. import org.apache.commons.configuration2.interpol.ConfigurationInterpolator;
  36. import org.apache.commons.configuration2.interpol.Lookup;
  37. import org.apache.commons.configuration2.io.ConfigurationLogger;
  38. import org.apache.commons.configuration2.tree.ImmutableNode;
  39. import org.apache.commons.configuration2.tree.NodeCombiner;

  40. /**
  41.  * <p>
  42.  * DynamicCombinedConfiguration allows a set of CombinedConfigurations to be used.
  43.  * </p>
  44.  * <p>
  45.  * Each CombinedConfiguration is referenced by a key that is dynamically constructed from a key pattern on each call.
  46.  * The key pattern will be resolved using the configured ConfigurationInterpolator.
  47.  * </p>
  48.  * <p>
  49.  * This Configuration implementation uses the configured {@code Synchronizer} to guard itself against concurrent access.
  50.  * If there are multiple threads accessing an instance concurrently, a fully functional {@code Synchronizer}
  51.  * implementation (for example {@code ReadWriteSynchronizer}) has to be used to ensure consistency and to avoid exceptions. The
  52.  * {@code Synchronizer} assigned to an instance is also passed to child configuration objects when they are created.
  53.  * </p>
  54.  *
  55.  * @since 1.6
  56.  */
  57. public class DynamicCombinedConfiguration extends CombinedConfiguration {
  58.     /**
  59.      * Internal class that identifies each Configuration.
  60.      */
  61.     static class ConfigData {
  62.         /** Stores a reference to the configuration. */
  63.         private final Configuration configuration;

  64.         /** Stores the name under which the configuration is stored. */
  65.         private final String name;

  66.         /** Stores the at string. */
  67.         private final String at;

  68.         /**
  69.          * Creates a new instance of {@code ConfigData} and initializes it.
  70.          *
  71.          * @param config the configuration
  72.          * @param n the name
  73.          * @param at the at position
  74.          */
  75.         public ConfigData(final Configuration config, final String n, final String at) {
  76.             configuration = config;
  77.             name = n;
  78.             this.at = at;
  79.         }

  80.         /**
  81.          * Gets the at position of this configuration.
  82.          *
  83.          * @return the at position
  84.          */
  85.         public String getAt() {
  86.             return at;
  87.         }

  88.         /**
  89.          * Gets the stored configuration.
  90.          *
  91.          * @return the configuration
  92.          */
  93.         public Configuration getConfiguration() {
  94.             return configuration;
  95.         }

  96.         /**
  97.          * Gets the configuration's name.
  98.          *
  99.          * @return the name
  100.          */
  101.         public String getName() {
  102.             return name;
  103.         }

  104.     }

  105.     /**
  106.      * A simple data class holding information about the current configuration while an operation for a thread is processed.
  107.      */
  108.     private static final class CurrentConfigHolder {
  109.         /** Stores the current configuration of the current thread. */
  110.         private CombinedConfiguration currentConfiguration;

  111.         /**
  112.          * Stores the key of the configuration evaluated for the current thread at the beginning of an operation.
  113.          */
  114.         private final String key;

  115.         /** A counter for reentrant locks. */
  116.         private int lockCount;

  117.         /**
  118.          * Creates a new instance of {@code CurrentConfigHolder} and initializes it with the key for the current configuration.
  119.          *
  120.          * @param curKey the current key
  121.          */
  122.         public CurrentConfigHolder(final String curKey) {
  123.             key = curKey;
  124.         }

  125.         /**
  126.          * Decrements the lock counter and checks whether it has reached 0. In this cause, the operation is complete, and the
  127.          * lock can be released.
  128.          *
  129.          * @return <strong>true</strong> if the lock count reaches 0, <strong>false</strong> otherwise
  130.          */
  131.         public boolean decrementLockCountAndCheckRelease() {
  132.             return --lockCount == 0;
  133.         }

  134.         /**
  135.          * Gets the current configuration.
  136.          *
  137.          * @return the current configuration
  138.          */
  139.         public CombinedConfiguration getCurrentConfiguration() {
  140.             return currentConfiguration;
  141.         }

  142.         /**
  143.          * Gets the current key.
  144.          *
  145.          * @return the current key
  146.          */
  147.         public String getKey() {
  148.             return key;
  149.         }

  150.         /**
  151.          * Increments the lock counter.
  152.          */
  153.         public void incrementLockCount() {
  154.             lockCount++;
  155.         }

  156.         /**
  157.          * Sets the current configuration.
  158.          *
  159.          * @param currentConfiguration the current configuration
  160.          */
  161.         public void setCurrentConfiguration(final CombinedConfiguration currentConfiguration) {
  162.             this.currentConfiguration = currentConfiguration;
  163.         }
  164.     }

  165.     /**
  166.      * Stores the current configuration for each involved thread. This value is set at the beginning of an operation and
  167.      * removed at the end.
  168.      */
  169.     private static final ThreadLocal<CurrentConfigHolder> CURRENT_CONFIG = new ThreadLocal<>();

  170.     /** The CombinedConfigurations */
  171.     private final ConcurrentMap<String, CombinedConfiguration> configs = new ConcurrentHashMap<>();

  172.     /** Stores a list with the contained configurations. */
  173.     private final List<ConfigData> configurations = new ArrayList<>();

  174.     /** Stores a map with the named configurations. */
  175.     private final Map<String, Configuration> namedConfigurations = new HashMap<>();

  176.     /** The key pattern for the CombinedConfiguration map. */
  177.     private String keyPattern;

  178.     /** Stores the combiner. */
  179.     private NodeCombiner nodeCombiner;

  180.     /** The name of the logger to use for each CombinedConfiguration */
  181.     private String loggerName = DynamicCombinedConfiguration.class.getName();

  182.     /** The object for handling variable substitution in key patterns. */
  183.     private final ConfigurationInterpolator localSubst;

  184.     /**
  185.      * Creates a new instance of {@code DynamicCombinedConfiguration} that uses a union combiner.
  186.      *
  187.      * @see org.apache.commons.configuration2.tree.UnionCombiner
  188.      */
  189.     public DynamicCombinedConfiguration() {
  190.         initLogger(new ConfigurationLogger(DynamicCombinedConfiguration.class));
  191.         localSubst = initLocalInterpolator();
  192.     }

  193.     /**
  194.      * Creates a new instance of {@code DynamicCombinedConfiguration} and initializes the combiner to be used.
  195.      *
  196.      * @param comb the node combiner (can be <strong>null</strong>, then a union combiner is used as default)
  197.      */
  198.     public DynamicCombinedConfiguration(final NodeCombiner comb) {
  199.         setNodeCombiner(comb);
  200.         initLogger(new ConfigurationLogger(DynamicCombinedConfiguration.class));
  201.         localSubst = initLocalInterpolator();
  202.     }

  203.     /**
  204.      * Adds a new configuration to this combined configuration. It is possible (but not mandatory) to give the new
  205.      * configuration a name. This name must be unique, otherwise a {@code ConfigurationRuntimeException} will be thrown.
  206.      * With the optional {@code at} argument you can specify where in the resulting node structure the content of the added
  207.      * configuration should appear. This is a string that uses dots as property delimiters (independent on the current
  208.      * expression engine). For instance if you pass in the string {@code "database.tables"}, all properties of the added
  209.      * configuration will occur in this branch.
  210.      *
  211.      * @param config the configuration to add (must not be <strong>null</strong>)
  212.      * @param name the name of this configuration (can be <strong>null</strong>)
  213.      * @param at the position of this configuration in the combined tree (can be <strong>null</strong>)
  214.      */
  215.     @Override
  216.     public void addConfiguration(final Configuration config, final String name, final String at) {
  217.         beginWrite(true);
  218.         try {
  219.             final ConfigData cd = new ConfigData(config, name, at);
  220.             configurations.add(cd);
  221.             if (name != null) {
  222.                 namedConfigurations.put(name, config);
  223.             }

  224.             // clear cache of all child configurations
  225.             configs.clear();
  226.         } finally {
  227.             endWrite();
  228.         }
  229.     }

  230.     @Override
  231.     public <T extends Event> void addEventListener(final EventType<T> eventType, final EventListener<? super T> listener) {
  232.         configs.values().forEach(cc -> cc.addEventListener(eventType, listener));
  233.         super.addEventListener(eventType, listener);
  234.     }

  235.     @Override
  236.     protected void addNodesInternal(final String key, final Collection<? extends ImmutableNode> nodes) {
  237.         getCurrentConfig().addNodes(key, nodes);
  238.     }

  239.     @Override
  240.     protected void addPropertyInternal(final String key, final Object value) {
  241.         getCurrentConfig().addProperty(key, value);
  242.     }

  243.     /**
  244.      * {@inheritDoc} This implementation ensures that the current configuration is initialized. The lock counter is
  245.      * increased.
  246.      */
  247.     @Override
  248.     protected void beginRead(final boolean optimize) {
  249.         final CurrentConfigHolder cch = ensureCurrentConfiguration();
  250.         cch.incrementLockCount();
  251.         if (!optimize && cch.getCurrentConfiguration() == null) {
  252.             // delegate to beginWrite() which creates the child configuration
  253.             beginWrite(false);
  254.             endWrite();
  255.         }

  256.         // This actually uses our own synchronizer
  257.         cch.getCurrentConfiguration().beginRead(optimize);
  258.     }

  259.     /**
  260.      * {@inheritDoc} This implementation ensures that the current configuration is initialized. If necessary, a new child
  261.      * configuration instance is created.
  262.      */
  263.     @Override
  264.     protected void beginWrite(final boolean optimize) {
  265.         final CurrentConfigHolder cch = ensureCurrentConfiguration();
  266.         cch.incrementLockCount();

  267.         super.beginWrite(optimize);
  268.         if (!optimize && cch.getCurrentConfiguration() == null) {
  269.             cch.setCurrentConfiguration(createChildConfiguration());
  270.             configs.put(cch.getKey(), cch.getCurrentConfiguration());
  271.             initChildConfiguration(cch.getCurrentConfiguration());
  272.         }
  273.     }

  274.     @Override
  275.     public void clearErrorListeners() {
  276.         configs.values().forEach(BaseEventSource::clearErrorListeners);
  277.         super.clearErrorListeners();
  278.     }

  279.     @Override
  280.     public void clearEventListeners() {
  281.         configs.values().forEach(CombinedConfiguration::clearEventListeners);
  282.         super.clearEventListeners();
  283.     }

  284.     @Override
  285.     protected void clearInternal() {
  286.         getCurrentConfig().clear();
  287.     }

  288.     @Override
  289.     protected void clearPropertyDirect(final String key) {
  290.         getCurrentConfig().clearProperty(key);
  291.     }

  292.     @Override
  293.     protected Object clearTreeInternal(final String key) {
  294.         getCurrentConfig().clearTree(key);
  295.         return Collections.emptyList();
  296.     }

  297.     @Override
  298.     public HierarchicalConfiguration<ImmutableNode> configurationAt(final String key) {
  299.         return getCurrentConfig().configurationAt(key);
  300.     }

  301.     @Override
  302.     public HierarchicalConfiguration<ImmutableNode> configurationAt(final String key, final boolean supportUpdates) {
  303.         return getCurrentConfig().configurationAt(key, supportUpdates);
  304.     }

  305.     @Override
  306.     public List<HierarchicalConfiguration<ImmutableNode>> configurationsAt(final String key) {
  307.         return getCurrentConfig().configurationsAt(key);
  308.     }

  309.     @Override
  310.     protected boolean containsKeyInternal(final String key) {
  311.         return getCurrentConfig().containsKey(key);
  312.     }

  313.     /**
  314.      * Tests whether this configuration contains one or more matches to this value. This operation stops at first
  315.      * match but may be more expensive than the containsKey method.
  316.      * @since 2.11.0
  317.      */
  318.     @Override
  319.     protected boolean containsValueInternal(final Object value) {
  320.         return getCurrentConfig().contains(getKeys(), value);
  321.     }

  322.     /**
  323.      * Creates a new, uninitialized child configuration.
  324.      *
  325.      * @return the new child configuration
  326.      */
  327.     private CombinedConfiguration createChildConfiguration() {
  328.         return new CombinedConfiguration(getNodeCombiner());
  329.     }

  330.     /**
  331.      * {@inheritDoc} This implementation clears the current configuration if necessary.
  332.      */
  333.     @Override
  334.     protected void endRead() {
  335.         CURRENT_CONFIG.get().getCurrentConfiguration().endRead();
  336.         releaseLock();
  337.     }

  338.     /**
  339.      * {@inheritDoc} This implementation clears the current configuration if necessary.
  340.      */
  341.     @Override
  342.     protected void endWrite() {
  343.         super.endWrite();
  344.         releaseLock();
  345.     }

  346.     /**
  347.      * Checks whether the current configuration is set. If not, a {@code CurrentConfigHolder} is now created and
  348.      * initialized, and associated with the current thread. The member for the current configuration is undefined if for the
  349.      * current key no configuration exists yet.
  350.      *
  351.      * @return the {@code CurrentConfigHolder} instance for the current thread
  352.      */
  353.     private CurrentConfigHolder ensureCurrentConfiguration() {
  354.         CurrentConfigHolder cch = CURRENT_CONFIG.get();
  355.         if (cch == null) {
  356.             final String key = String.valueOf(localSubst.interpolate(keyPattern));
  357.             cch = new CurrentConfigHolder(key);
  358.             cch.setCurrentConfiguration(configs.get(key));
  359.             CURRENT_CONFIG.set(cch);
  360.         }
  361.         return cch;
  362.     }

  363.     @Override
  364.     public BigDecimal getBigDecimal(final String key) {
  365.         return getCurrentConfig().getBigDecimal(key);
  366.     }

  367.     @Override
  368.     public BigDecimal getBigDecimal(final String key, final BigDecimal defaultValue) {
  369.         return getCurrentConfig().getBigDecimal(key, defaultValue);
  370.     }

  371.     @Override
  372.     public BigInteger getBigInteger(final String key) {
  373.         return getCurrentConfig().getBigInteger(key);
  374.     }

  375.     @Override
  376.     public BigInteger getBigInteger(final String key, final BigInteger defaultValue) {
  377.         return getCurrentConfig().getBigInteger(key, defaultValue);
  378.     }

  379.     @Override
  380.     public boolean getBoolean(final String key) {
  381.         return getCurrentConfig().getBoolean(key);
  382.     }

  383.     @Override
  384.     public boolean getBoolean(final String key, final boolean defaultValue) {
  385.         return getCurrentConfig().getBoolean(key, defaultValue);
  386.     }

  387.     @Override
  388.     public Boolean getBoolean(final String key, final Boolean defaultValue) {
  389.         return getCurrentConfig().getBoolean(key, defaultValue);
  390.     }

  391.     @Override
  392.     public byte getByte(final String key) {
  393.         return getCurrentConfig().getByte(key);
  394.     }

  395.     @Override
  396.     public byte getByte(final String key, final byte defaultValue) {
  397.         return getCurrentConfig().getByte(key, defaultValue);
  398.     }

  399.     @Override
  400.     public Byte getByte(final String key, final Byte defaultValue) {
  401.         return getCurrentConfig().getByte(key, defaultValue);
  402.     }

  403.     /**
  404.      * Gets the configuration at the specified index. The contained configurations are numbered in the order they were
  405.      * added to this combined configuration. The index of the first configuration is 0.
  406.      *
  407.      * @param index the index
  408.      * @return the configuration at this index
  409.      */
  410.     @Override
  411.     public Configuration getConfiguration(final int index) {
  412.         beginRead(false);
  413.         try {
  414.             final ConfigData cd = configurations.get(index);
  415.             return cd.getConfiguration();
  416.         } finally {
  417.             endRead();
  418.         }
  419.     }

  420.     /**
  421.      * Gets the configuration with the given name. This can be <strong>null</strong> if no such configuration exists.
  422.      *
  423.      * @param name the name of the configuration
  424.      * @return the configuration with this name
  425.      */
  426.     @Override
  427.     public Configuration getConfiguration(final String name) {
  428.         beginRead(false);
  429.         try {
  430.             return namedConfigurations.get(name);
  431.         } finally {
  432.             endRead();
  433.         }
  434.     }

  435.     /**
  436.      * Gets a set with the names of all configurations contained in this combined configuration. Of course here are only
  437.      * these configurations listed, for which a name was specified when they were added.
  438.      *
  439.      * @return a set with the names of the contained configurations (never <strong>null</strong>)
  440.      */
  441.     @Override
  442.     public Set<String> getConfigurationNames() {
  443.         beginRead(false);
  444.         try {
  445.             return namedConfigurations.keySet();
  446.         } finally {
  447.             endRead();
  448.         }
  449.     }

  450.     /**
  451.      * Gets the current configuration. This configuration was initialized at the beginning of an operation and stored in
  452.      * a thread-local variable. Some methods of this class call this method directly without requesting a lock before. To
  453.      * deal with this, we always request an additional read lock.
  454.      *
  455.      * @return the current configuration
  456.      */
  457.     private CombinedConfiguration getCurrentConfig() {
  458.         CombinedConfiguration config;
  459.         String key;
  460.         beginRead(false);
  461.         try {
  462.             config = CURRENT_CONFIG.get().getCurrentConfiguration();
  463.             key = CURRENT_CONFIG.get().getKey();
  464.         } finally {
  465.             endRead();
  466.         }

  467.         if (getLogger().isDebugEnabled()) {
  468.             getLogger().debug("Returning config for " + key + ": " + config);
  469.         }
  470.         return config;
  471.     }

  472.     @Override
  473.     public double getDouble(final String key) {
  474.         return getCurrentConfig().getDouble(key);
  475.     }

  476.     @Override
  477.     public double getDouble(final String key, final double defaultValue) {
  478.         return getCurrentConfig().getDouble(key, defaultValue);
  479.     }

  480.     @Override
  481.     public Double getDouble(final String key, final Double defaultValue) {
  482.         return getCurrentConfig().getDouble(key, defaultValue);
  483.     }

  484.     @Override
  485.     public float getFloat(final String key) {
  486.         return getCurrentConfig().getFloat(key);
  487.     }

  488.     @Override
  489.     public float getFloat(final String key, final float defaultValue) {
  490.         return getCurrentConfig().getFloat(key, defaultValue);
  491.     }

  492.     @Override
  493.     public Float getFloat(final String key, final Float defaultValue) {
  494.         return getCurrentConfig().getFloat(key, defaultValue);
  495.     }

  496.     @Override
  497.     public int getInt(final String key) {
  498.         return getCurrentConfig().getInt(key);
  499.     }

  500.     @Override
  501.     public int getInt(final String key, final int defaultValue) {
  502.         return getCurrentConfig().getInt(key, defaultValue);
  503.     }

  504.     @Override
  505.     public Integer getInteger(final String key, final Integer defaultValue) {
  506.         return getCurrentConfig().getInteger(key, defaultValue);
  507.     }

  508.     /**
  509.      * Gets the key pattern for the CombinedConfiguration map.
  510.      *
  511.      * @return the key pattern for the CombinedConfiguration map.
  512.      */
  513.     public String getKeyPattern() {
  514.         return this.keyPattern;
  515.     }

  516.     @Override
  517.     protected Iterator<String> getKeysInternal() {
  518.         return getCurrentConfig().getKeys();
  519.     }

  520.     @Override
  521.     protected Iterator<String> getKeysInternal(final String prefix) {
  522.         return getCurrentConfig().getKeys(prefix);
  523.     }

  524.     @Override
  525.     public List<Object> getList(final String key) {
  526.         return getCurrentConfig().getList(key);
  527.     }

  528.     @Override
  529.     public List<Object> getList(final String key, final List<?> defaultValue) {
  530.         return getCurrentConfig().getList(key, defaultValue);
  531.     }

  532.     @Override
  533.     public long getLong(final String key) {
  534.         return getCurrentConfig().getLong(key);
  535.     }

  536.     @Override
  537.     public long getLong(final String key, final long defaultValue) {
  538.         return getCurrentConfig().getLong(key, defaultValue);
  539.     }

  540.     @Override
  541.     public Long getLong(final String key, final Long defaultValue) {
  542.         return getCurrentConfig().getLong(key, defaultValue);
  543.     }

  544.     @Override
  545.     protected int getMaxIndexInternal(final String key) {
  546.         return getCurrentConfig().getMaxIndex(key);
  547.     }

  548.     /**
  549.      * Gets the node combiner that is used for creating the combined node structure.
  550.      *
  551.      * @return the node combiner
  552.      */
  553.     @Override
  554.     public NodeCombiner getNodeCombiner() {
  555.         return nodeCombiner;
  556.     }

  557.     /**
  558.      * Gets the number of configurations that are contained in this combined configuration.
  559.      *
  560.      * @return the number of contained configurations
  561.      */
  562.     @Override
  563.     public int getNumberOfConfigurations() {
  564.         beginRead(false);
  565.         try {
  566.             return configurations.size();
  567.         } finally {
  568.             endRead();
  569.         }
  570.     }

  571.     @Override
  572.     public Properties getProperties(final String key) {
  573.         return getCurrentConfig().getProperties(key);
  574.     }

  575.     @Override
  576.     protected Object getPropertyInternal(final String key) {
  577.         return getCurrentConfig().getProperty(key);
  578.     }

  579.     @Override
  580.     public short getShort(final String key) {
  581.         return getCurrentConfig().getShort(key);
  582.     }

  583.     @Override
  584.     public short getShort(final String key, final short defaultValue) {
  585.         return getCurrentConfig().getShort(key, defaultValue);
  586.     }

  587.     @Override
  588.     public Short getShort(final String key, final Short defaultValue) {
  589.         return getCurrentConfig().getShort(key, defaultValue);
  590.     }

  591.     /**
  592.      * Gets the configuration source, in which the specified key is defined. This method will determine the configuration
  593.      * node that is identified by the given key. The following constellations are possible:
  594.      * <ul>
  595.      * <li>If no node object is found for this key, <strong>null</strong> is returned.</li>
  596.      * <li>If the key maps to multiple nodes belonging to different configuration sources, a
  597.      * {@code IllegalArgumentException} is thrown (in this case no unique source can be determined).</li>
  598.      * <li>If exactly one node is found for the key, the (child) configuration object, to which the node belongs is
  599.      * determined and returned.</li>
  600.      * <li>For keys that have been added directly to this combined configuration and that do not belong to the namespaces
  601.      * defined by existing child configurations this configuration will be returned.</li>
  602.      * </ul>
  603.      *
  604.      * @param key the key of a configuration property
  605.      * @return the configuration, to which this property belongs or <strong>null</strong> if the key cannot be resolved
  606.      * @throws IllegalArgumentException if the key maps to multiple properties and the source cannot be determined, or if
  607.      *         the key is <strong>null</strong>
  608.      */
  609.     @Override
  610.     public Configuration getSource(final String key) {
  611.         if (key == null) {
  612.             throw new IllegalArgumentException("Key must not be null!");
  613.         }
  614.         return getCurrentConfig().getSource(key);
  615.     }

  616.     @Override
  617.     public String getString(final String key) {
  618.         return getCurrentConfig().getString(key);
  619.     }

  620.     @Override
  621.     public String getString(final String key, final String defaultValue) {
  622.         return getCurrentConfig().getString(key, defaultValue);
  623.     }

  624.     @Override
  625.     public String[] getStringArray(final String key) {
  626.         return getCurrentConfig().getStringArray(key);
  627.     }

  628.     /**
  629.      * Initializes a newly created child configuration. This method copies a bunch of settings from this instance to the
  630.      * child configuration.
  631.      *
  632.      * @param config the child configuration to be initialized
  633.      */
  634.     private void initChildConfiguration(final CombinedConfiguration config) {
  635.         if (loggerName != null) {
  636.             config.setLogger(new ConfigurationLogger(loggerName));
  637.         }
  638.         config.setExpressionEngine(getExpressionEngine());
  639.         config.setConversionExpressionEngine(getConversionExpressionEngine());
  640.         config.setListDelimiterHandler(getListDelimiterHandler());
  641.         copyEventListeners(config);
  642.         configurations.forEach(data -> config.addConfiguration(data.getConfiguration(), data.getName(), data.getAt()));
  643.         config.setSynchronizer(getSynchronizer());
  644.     }

  645.     /**
  646.      * Creates a {@code ConfigurationInterpolator} instance for performing local variable substitutions. This implementation
  647.      * returns an object which shares the prefix lookups from this configuration's {@code ConfigurationInterpolator}, but
  648.      * does not define any other lookups.
  649.      *
  650.      * @return the {@code ConfigurationInterpolator}
  651.      */
  652.     private ConfigurationInterpolator initLocalInterpolator() {
  653.         return new ConfigurationInterpolator() {
  654.             @Override
  655.             protected Lookup fetchLookupForPrefix(final String prefix) {
  656.                 return nullSafeLookup(getInterpolator().getLookups().get(prefix));
  657.             }
  658.         };
  659.     }

  660.     @Override
  661.     public Configuration interpolatedConfiguration() {
  662.         return getCurrentConfig().interpolatedConfiguration();
  663.     }

  664.     /**
  665.      * Invalidates the current combined configuration. This means that the next time a property is accessed the combined
  666.      * node structure must be re-constructed. Invalidation of a combined configuration also means that an event of type
  667.      * {@code EVENT_COMBINED_INVALIDATE} is fired. Note that while other events most times appear twice (once before and
  668.      * once after an update), this event is only fired once (after update).
  669.      */
  670.     @Override
  671.     public void invalidate() {
  672.         getCurrentConfig().invalidate();
  673.     }

  674.     /**
  675.      * Invalidates all CombinedConfigurations.
  676.      */
  677.     public void invalidateAll() {
  678.         configs.values().forEach(CombinedConfiguration::invalidate);
  679.     }

  680.     @Override
  681.     protected boolean isEmptyInternal() {
  682.         return getCurrentConfig().isEmpty();
  683.     }

  684.     /**
  685.      * Decrements the lock count of the current configuration holder. If it reaches 0, the current configuration is removed.
  686.      * (It is then reevaluated when the next operation starts.)
  687.      */
  688.     private void releaseLock() {
  689.         final CurrentConfigHolder cch = CURRENT_CONFIG.get();
  690.         assert cch != null : "No current configuration!";
  691.         if (cch.decrementLockCountAndCheckRelease()) {
  692.             CURRENT_CONFIG.remove();
  693.         }
  694.     }

  695.     /**
  696.      * Removes the specified configuration from this combined configuration.
  697.      *
  698.      * @param config the configuration to be removed
  699.      * @return a flag whether this configuration was found and could be removed
  700.      */
  701.     @Override
  702.     public boolean removeConfiguration(final Configuration config) {
  703.         beginWrite(false);
  704.         try {
  705.             for (int index = 0; index < getNumberOfConfigurations(); index++) {
  706.                 if (configurations.get(index).getConfiguration() == config) {
  707.                     removeConfigurationAt(index);
  708.                     return true;
  709.                 }
  710.             }

  711.             return false;
  712.         } finally {
  713.             endWrite();
  714.         }
  715.     }

  716.     /**
  717.      * Removes the configuration with the specified name.
  718.      *
  719.      * @param name the name of the configuration to be removed
  720.      * @return the removed configuration (<strong>null</strong> if this configuration was not found)
  721.      */
  722.     @Override
  723.     public Configuration removeConfiguration(final String name) {
  724.         final Configuration conf = getConfiguration(name);
  725.         if (conf != null) {
  726.             removeConfiguration(conf);
  727.         }
  728.         return conf;
  729.     }

  730.     /**
  731.      * Removes the configuration at the specified index.
  732.      *
  733.      * @param index the index
  734.      * @return the removed configuration
  735.      */
  736.     @Override
  737.     public Configuration removeConfigurationAt(final int index) {
  738.         beginWrite(false);
  739.         try {
  740.             final ConfigData cd = configurations.remove(index);
  741.             if (cd.getName() != null) {
  742.                 namedConfigurations.remove(cd.getName());
  743.             }
  744.             return cd.getConfiguration();
  745.         } finally {
  746.             endWrite();
  747.         }
  748.     }

  749.     @Override
  750.     public <T extends Event> boolean removeEventListener(final EventType<T> eventType, final EventListener<? super T> listener) {
  751.         configs.values().forEach(cc -> cc.removeEventListener(eventType, listener));
  752.         return super.removeEventListener(eventType, listener);
  753.     }

  754.     /**
  755.      * Sets the key pattern for the CombinedConfiguration map.
  756.      *
  757.      * @param pattern the key pattern for the CombinedConfiguration map.
  758.      */
  759.     public void setKeyPattern(final String pattern) {
  760.         this.keyPattern = pattern;
  761.     }

  762.     /**
  763.      * Sets the name of the Logger to use on each CombinedConfiguration.
  764.      *
  765.      * @param name The Logger name.
  766.      */
  767.     public void setLoggerName(final String name) {
  768.         this.loggerName = name;
  769.     }

  770.     /**
  771.      * Sets the node combiner. This object will be used when the combined node structure is to be constructed. It must not
  772.      * be <strong>null</strong>, otherwise an {@code IllegalArgumentException} exception is thrown. Changing the node combiner causes
  773.      * an invalidation of this combined configuration, so that the new combiner immediately takes effect.
  774.      *
  775.      * @param nodeCombiner the node combiner
  776.      */
  777.     @Override
  778.     public void setNodeCombiner(final NodeCombiner nodeCombiner) {
  779.         if (nodeCombiner == null) {
  780.             throw new IllegalArgumentException("Node combiner must not be null!");
  781.         }
  782.         this.nodeCombiner = nodeCombiner;
  783.         invalidateAll();
  784.     }

  785.     @Override
  786.     protected void setPropertyInternal(final String key, final Object value) {
  787.         getCurrentConfig().setProperty(key, value);
  788.     }

  789.     @Override
  790.     protected int sizeInternal() {
  791.         return getCurrentConfig().size();
  792.     }

  793.     @Override
  794.     public Configuration subset(final String prefix) {
  795.         return getCurrentConfig().subset(prefix);
  796.     }
  797. }