    001/*
    002 * Licensed to the Apache Software Foundation (ASF) under one or more
    003 * contributor license agreements.  See the NOTICE file distributed with
    004 * this work for additional information regarding copyright ownership.
    005 * The ASF licenses this file to You under the Apache License, Version 2.0
    006 * (the "License"); you may not use this file except in compliance with
    007 * the License.  You may obtain a copy of the License at
    008 *
    009 *     http://www.apache.org/licenses/LICENSE-2.0
    010 *
    011 * Unless required by applicable law or agreed to in writing, software
    012 * distributed under the License is distributed on an "AS IS" BASIS,
    013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014 * See the License for the specific language governing permissions and
    015 * limitations under the License.
    016 */
    017package org.apache.commons.configuration2;
    018
    019import java.math.BigDecimal;
    020import java.math.BigInteger;
    021import java.util.ArrayList;
    022import java.util.Collection;
    023import java.util.Collections;
    024import java.util.HashMap;
    025import java.util.Iterator;
    026import java.util.List;
    027import java.util.Map;
    028import java.util.Properties;
    029import java.util.Set;
    030import java.util.concurrent.ConcurrentHashMap;
    031import java.util.concurrent.ConcurrentMap;
    032
    033import org.apache.commons.configuration2.event.BaseEventSource;
    034import org.apache.commons.configuration2.event.Event;
    035import org.apache.commons.configuration2.event.EventListener;
    036import org.apache.commons.configuration2.event.EventType;
    037import org.apache.commons.configuration2.interpol.ConfigurationInterpolator;
    038import org.apache.commons.configuration2.interpol.Lookup;
    039import org.apache.commons.configuration2.io.ConfigurationLogger;
    040import org.apache.commons.configuration2.tree.ImmutableNode;
    041import org.apache.commons.configuration2.tree.NodeCombiner;
    042
    043/**
    044 * <p>
    045 * DynamicCombinedConfiguration allows a set of CombinedConfigurations to be used.
    046 * </p>
    047 * <p>
    048 * Each CombinedConfiguration is referenced by a key that is dynamically constructed from a key pattern on each call.
    049 * The key pattern will be resolved using the configured ConfigurationInterpolator.
    050 * </p>
    051 * <p>
    052 * This Configuration implementation uses the configured {@code Synchronizer} to guard itself against concurrent access.
    053 * If there are multiple threads accessing an instance concurrently, a fully functional {@code Synchronizer}
    054 * implementation (e.g. {@code ReadWriteSynchronizer}) has to be used to ensure consistency and to avoid exceptions. The
    055 * {@code Synchronizer} assigned to an instance is also passed to child configuration objects when they are created.
    056 * </p>
    057 *
    058 * @since 1.6
    059 */
    060public class DynamicCombinedConfiguration extends CombinedConfiguration {
    061    /**
    062     * Internal class that identifies each Configuration.
    063     */
    064    static class ConfigData {
    065        /** Stores a reference to the configuration. */
    066        private final Configuration configuration;
    067
    068        /** Stores the name under which the configuration is stored. */
    069        private final String name;
    070
    071        /** Stores the at string. */
    072        private final String at;
    073
    074        /**
    075         * Creates a new instance of {@code ConfigData} and initializes it.
    076         *
    077         * @param config the configuration
    078         * @param n the name
    079         * @param at the at position
    080         */
    081        public ConfigData(final Configuration config, final String n, final String at) {
    082            configuration = config;
    083            name = n;
    084            this.at = at;
    085        }
    086
    087        /**
    088         * Gets the at position of this configuration.
    089         *
    090         * @return the at position
    091         */
    092        public String getAt() {
    093            return at;
    094        }
    095
    096        /**
    097         * Gets the stored configuration.
    098         *
    099         * @return the configuration
    100         */
    101        public Configuration getConfiguration() {
    102            return configuration;
    103        }
    104
    105        /**
    106         * Gets the configuration's name.
    107         *
    108         * @return the name
    109         */
    110        public String getName() {
    111            return name;
    112        }
    113
    114    }
    115
    116    /**
    117     * A simple data class holding information about the current configuration while an operation for a thread is processed.
    118     */
    119    private static final class CurrentConfigHolder {
    120        /** Stores the current configuration of the current thread. */
    121        private CombinedConfiguration currentConfiguration;
    122
    123        /**
    124         * Stores the key of the configuration evaluated for the current thread at the beginning of an operation.
    125         */
    126        private final String key;
    127
    128        /** A counter for reentrant locks. */
    129        private int lockCount;
    130
    131        /**
    132         * Creates a new instance of {@code CurrentConfigHolder} and initializes it with the key for the current configuration.
    133         *
    134         * @param curKey the current key
    135         */
    136        public CurrentConfigHolder(final String curKey) {
    137            key = curKey;
    138        }
    139
    140        /**
    141         * Decrements the lock counter and checks whether it has reached 0. In this cause, the operation is complete, and the
    142         * lock can be released.
    143         *
    144         * @return <b>true</b> if the lock count reaches 0, <b>false</b> otherwise
    145         */
    146        public boolean decrementLockCountAndCheckRelease() {
    147            return --lockCount == 0;
    148        }
    149
    150        /**
    151         * Gets the current configuration.
    152         *
    153         * @return the current configuration
    154         */
    155        public CombinedConfiguration getCurrentConfiguration() {
    156            return currentConfiguration;
    157        }
    158
    159        /**
    160         * Gets the current key.
    161         *
    162         * @return the current key
    163         */
    164        public String getKey() {
    165            return key;
    166        }
    167
    168        /**
    169         * Increments the lock counter.
    170         */
    171        public void incrementLockCount() {
    172            lockCount++;
    173        }
    174
    175        /**
    176         * Sets the current configuration.
    177         *
    178         * @param currentConfiguration the current configuration
    179         */
    180        public void setCurrentConfiguration(final CombinedConfiguration currentConfiguration) {
    181            this.currentConfiguration = currentConfiguration;
    182        }
    183    }
    184
    185    /**
    186     * Stores the current configuration for each involved thread. This value is set at the beginning of an operation and
    187     * removed at the end.
    188     */
    189    private static final ThreadLocal<CurrentConfigHolder> CURRENT_CONFIG = new ThreadLocal<>();
    190
    191    /** The CombinedConfigurations */
    192    private final ConcurrentMap<String, CombinedConfiguration> configs = new ConcurrentHashMap<>();
    193
    194    /** Stores a list with the contained configurations. */
    195    private final List<ConfigData> configurations = new ArrayList<>();
    196
    197    /** Stores a map with the named configurations. */
    198    private final Map<String, Configuration> namedConfigurations = new HashMap<>();
    199
    200    /** The key pattern for the CombinedConfiguration map */
    201    private String keyPattern;
    202
    203    /** Stores the combiner. */
    204    private NodeCombiner nodeCombiner;
    205
    206    /** The name of the logger to use for each CombinedConfiguration */
    207    private String loggerName = DynamicCombinedConfiguration.class.getName();
    208
    209    /** The object for handling variable substitution in key patterns. */
    210    private final ConfigurationInterpolator localSubst;
    211
    212    /**
    213     * Creates a new instance of {@code DynamicCombinedConfiguration} that uses a union combiner.
    214     *
    215     * @see org.apache.commons.configuration2.tree.UnionCombiner
    216     */
    217    public DynamicCombinedConfiguration() {
    218        initLogger(new ConfigurationLogger(DynamicCombinedConfiguration.class));
    219        localSubst = initLocalInterpolator();
    220    }
    221
    222    /**
    223     * Creates a new instance of {@code DynamicCombinedConfiguration} and initializes the combiner to be used.
    224     *
    225     * @param comb the node combiner (can be <b>null</b>, then a union combiner is used as default)
    226     */
    227    public DynamicCombinedConfiguration(final NodeCombiner comb) {
    228        setNodeCombiner(comb);
    229        initLogger(new ConfigurationLogger(DynamicCombinedConfiguration.class));
    230        localSubst = initLocalInterpolator();
    231    }
    232
    233    /**
    234     * Adds a new configuration to this combined configuration. It is possible (but not mandatory) to give the new
    235     * configuration a name. This name must be unique, otherwise a {@code ConfigurationRuntimeException} will be thrown.
    236     * With the optional {@code at} argument you can specify where in the resulting node structure the content of the added
    237     * configuration should appear. This is a string that uses dots as property delimiters (independent on the current
    238     * expression engine). For instance if you pass in the string {@code "database.tables"}, all properties of the added
    239     * configuration will occur in this branch.
    240     *
    241     * @param config the configuration to add (must not be <b>null</b>)
    242     * @param name the name of this configuration (can be <b>null</b>)
    243     * @param at the position of this configuration in the combined tree (can be <b>null</b>)
    244     */
    245    @Override
    246    public void addConfiguration(final Configuration config, final String name, final String at) {
    247        beginWrite(true);
    248        try {
    249            final ConfigData cd = new ConfigData(config, name, at);
    250            configurations.add(cd);
    251            if (name != null) {
    252                namedConfigurations.put(name, config);
    253            }
    254
    255            // clear cache of all child configurations
    256            configs.clear();
    257        } finally {
    258            endWrite();
    259        }
    260    }
    261
    262    @Override
    263    public <T extends Event> void addEventListener(final EventType<T> eventType, final EventListener<? super T> listener) {
    264        configs.values().forEach(cc -> cc.addEventListener(eventType, listener));
    265        super.addEventListener(eventType, listener);
    266    }
    267
    268    @Override
    269    protected void addNodesInternal(final String key, final Collection<? extends ImmutableNode> nodes) {
    270        getCurrentConfig().addNodes(key, nodes);
    271    }
    272
    273    @Override
    274    protected void addPropertyInternal(final String key, final Object value) {
    275        getCurrentConfig().addProperty(key, value);
    276    }
    277
    278    /**
    279     * {@inheritDoc} This implementation ensures that the current configuration is initialized. The lock counter is
    280     * increased.
    281     */
    282    @Override
    283    protected void beginRead(final boolean optimize) {
    284        final CurrentConfigHolder cch = ensureCurrentConfiguration();
    285        cch.incrementLockCount();
    286        if (!optimize && cch.getCurrentConfiguration() == null) {
    287            // delegate to beginWrite() which creates the child configuration
    288            beginWrite(false);
    289            endWrite();
    290        }
    291
    292        // This actually uses our own synchronizer
    293        cch.getCurrentConfiguration().beginRead(optimize);
    294    }
    295
    296    /**
    297     * {@inheritDoc} This implementation ensures that the current configuration is initialized. If necessary, a new child
    298     * configuration instance is created.
    299     */
    300    @Override
    301    protected void beginWrite(final boolean optimize) {
    302        final CurrentConfigHolder cch = ensureCurrentConfiguration();
    303        cch.incrementLockCount();
    304
    305        super.beginWrite(optimize);
    306        if (!optimize && cch.getCurrentConfiguration() == null) {
    307            cch.setCurrentConfiguration(createChildConfiguration());
    308            configs.put(cch.getKey(), cch.getCurrentConfiguration());
    309            initChildConfiguration(cch.getCurrentConfiguration());
    310        }
    311    }
    312
    313    @Override
    314    public void clearErrorListeners() {
    315        configs.values().forEach(BaseEventSource::clearErrorListeners);
    316        super.clearErrorListeners();
    317    }
    318
    319    @Override
    320    public void clearEventListeners() {
    321        configs.values().forEach(CombinedConfiguration::clearEventListeners);
    322        super.clearEventListeners();
    323    }
    324
    325    @Override
    326    protected void clearInternal() {
    327        getCurrentConfig().clear();
    328    }
    329
    330    @Override
    331    protected void clearPropertyDirect(final String key) {
    332        getCurrentConfig().clearProperty(key);
    333    }
    334
    335    @Override
    336    protected Object clearTreeInternal(final String key) {
    337        getCurrentConfig().clearTree(key);
    338        return Collections.emptyList();
    339    }
    340
    341    @Override
    342    public HierarchicalConfiguration<ImmutableNode> configurationAt(final String key) {
    343        return getCurrentConfig().configurationAt(key);
    344    }
    345
    346    @Override
    347    public HierarchicalConfiguration<ImmutableNode> configurationAt(final String key, final boolean supportUpdates) {
    348        return getCurrentConfig().configurationAt(key, supportUpdates);
    349    }
    350
    351    @Override
    352    public List<HierarchicalConfiguration<ImmutableNode>> configurationsAt(final String key) {
    353        return getCurrentConfig().configurationsAt(key);
    354    }
    355
    356    @Override
    357    protected boolean containsKeyInternal(final String key) {
    358        return getCurrentConfig().containsKey(key);
    359    }
    360
    361    /**
    362     * Tests whether this configuration contains one or more matches to this value. This operation stops at first
    363     * match but may be more expensive than the containsKey method.
    364     * @since 2.11.0
    365     */
    366    @Override
    367    protected boolean containsValueInternal(final Object value) {
    368        return getCurrentConfig().contains(getKeys(), value);
    369    }
    370
    371    /**
    372     * Creates a new, uninitialized child configuration.
    373     *
    374     * @return the new child configuration
    375     */
    376    private CombinedConfiguration createChildConfiguration() {
    377        return new CombinedConfiguration(getNodeCombiner());
    378    }
    379
    380    /**
    381     * {@inheritDoc} This implementation clears the current configuration if necessary.
    382     */
    383    @Override
    384    protected void endRead() {
    385        CURRENT_CONFIG.get().getCurrentConfiguration().endRead();
    386        releaseLock();
    387    }
    388
    389    /**
    390     * {@inheritDoc} This implementation clears the current configuration if necessary.
    391     */
    392    @Override
    393    protected void endWrite() {
    394        super.endWrite();
    395        releaseLock();
    396    }
    397
    398    /**
    399     * Checks whether the current configuration is set. If not, a {@code CurrentConfigHolder} is now created and
    400     * initialized, and associated with the current thread. The member for the current configuration is undefined if for the
    401     * current key no configuration exists yet.
    402     *
    403     * @return the {@code CurrentConfigHolder} instance for the current thread
    404     */
    405    private CurrentConfigHolder ensureCurrentConfiguration() {
    406        CurrentConfigHolder cch = CURRENT_CONFIG.get();
    407        if (cch == null) {
    408            final String key = String.valueOf(localSubst.interpolate(keyPattern));
    409            cch = new CurrentConfigHolder(key);
    410            cch.setCurrentConfiguration(configs.get(key));
    411            CURRENT_CONFIG.set(cch);
    412        }
    413        return cch;
    414    }
    415
    416    @Override
    417    public BigDecimal getBigDecimal(final String key) {
    418        return getCurrentConfig().getBigDecimal(key);
    419    }
    420
    421    @Override
    422    public BigDecimal getBigDecimal(final String key, final BigDecimal defaultValue) {
    423        return getCurrentConfig().getBigDecimal(key, defaultValue);
    424    }
    425
    426    @Override
    427    public BigInteger getBigInteger(final String key) {
    428        return getCurrentConfig().getBigInteger(key);
    429    }
    430
    431    @Override
    432    public BigInteger getBigInteger(final String key, final BigInteger defaultValue) {
    433        return getCurrentConfig().getBigInteger(key, defaultValue);
    434    }
    435
    436    @Override
    437    public boolean getBoolean(final String key) {
    438        return getCurrentConfig().getBoolean(key);
    439    }
    440
    441    @Override
    442    public boolean getBoolean(final String key, final boolean defaultValue) {
    443        return getCurrentConfig().getBoolean(key, defaultValue);
    444    }
    445
    446    @Override
    447    public Boolean getBoolean(final String key, final Boolean defaultValue) {
    448        return getCurrentConfig().getBoolean(key, defaultValue);
    449    }
    450
    451    @Override
    452    public byte getByte(final String key) {
    453        return getCurrentConfig().getByte(key);
    454    }
    455
    456    @Override
    457    public byte getByte(final String key, final byte defaultValue) {
    458        return getCurrentConfig().getByte(key, defaultValue);
    459    }
    460
    461    @Override
    462    public Byte getByte(final String key, final Byte defaultValue) {
    463        return getCurrentConfig().getByte(key, defaultValue);
    464    }
    465
    466    /**
    467     * Gets the configuration at the specified index. The contained configurations are numbered in the order they were
    468     * added to this combined configuration. The index of the first configuration is 0.
    469     *
    470     * @param index the index
    471     * @return the configuration at this index
    472     */
    473    @Override
    474    public Configuration getConfiguration(final int index) {
    475        beginRead(false);
    476        try {
    477            final ConfigData cd = configurations.get(index);
    478            return cd.getConfiguration();
    479        } finally {
    480            endRead();
    481        }
    482    }
    483
    484    /**
    485     * Gets the configuration with the given name. This can be <b>null</b> if no such configuration exists.
    486     *
    487     * @param name the name of the configuration
    488     * @return the configuration with this name
    489     */
    490    @Override
    491    public Configuration getConfiguration(final String name) {
    492        beginRead(false);
    493        try {
    494            return namedConfigurations.get(name);
    495        } finally {
    496            endRead();
    497        }
    498    }
    499
    500    /**
    501     * Gets a set with the names of all configurations contained in this combined configuration. Of course here are only
    502     * these configurations listed, for which a name was specified when they were added.
    503     *
    504     * @return a set with the names of the contained configurations (never <b>null</b>)
    505     */
    506    @Override
    507    public Set<String> getConfigurationNames() {
    508        beginRead(false);
    509        try {
    510            return namedConfigurations.keySet();
    511        } finally {
    512            endRead();
    513        }
    514    }
    515
    516    /**
    517     * Gets the current configuration. This configuration was initialized at the beginning of an operation and stored in
    518     * a thread-local variable. Some methods of this class call this method directly without requesting a lock before. To
    519     * deal with this, we always request an additional read lock.
    520     *
    521     * @return the current configuration
    522     */
    523    private CombinedConfiguration getCurrentConfig() {
    524        CombinedConfiguration config;
    525        String key;
    526        beginRead(false);
    527        try {
    528            config = CURRENT_CONFIG.get().getCurrentConfiguration();
    529            key = CURRENT_CONFIG.get().getKey();
    530        } finally {
    531            endRead();
    532        }
    533
    534        if (getLogger().isDebugEnabled()) {
    535            getLogger().debug("Returning config for " + key + ": " + config);
    536        }
    537        return config;
    538    }
    539
    540    @Override
    541    public double getDouble(final String key) {
    542        return getCurrentConfig().getDouble(key);
    543    }
    544
    545    @Override
    546    public double getDouble(final String key, final double defaultValue) {
    547        return getCurrentConfig().getDouble(key, defaultValue);
    548    }
    549
    550    @Override
    551    public Double getDouble(final String key, final Double defaultValue) {
    552        return getCurrentConfig().getDouble(key, defaultValue);
    553    }
    554
    555    @Override
    556    public float getFloat(final String key) {
    557        return getCurrentConfig().getFloat(key);
    558    }
    559
    560    @Override
    561    public float getFloat(final String key, final float defaultValue) {
    562        return getCurrentConfig().getFloat(key, defaultValue);
    563    }
    564
    565    @Override
    566    public Float getFloat(final String key, final Float defaultValue) {
    567        return getCurrentConfig().getFloat(key, defaultValue);
    568    }
    569
    570    @Override
    571    public int getInt(final String key) {
    572        return getCurrentConfig().getInt(key);
    573    }
    574
    575    @Override
    576    public int getInt(final String key, final int defaultValue) {
    577        return getCurrentConfig().getInt(key, defaultValue);
    578    }
    579
    580    @Override
    581    public Integer getInteger(final String key, final Integer defaultValue) {
    582        return getCurrentConfig().getInteger(key, defaultValue);
    583    }
    584
    585    public String getKeyPattern() {
    586        return this.keyPattern;
    587    }
    588
    589    @Override
    590    protected Iterator<String> getKeysInternal() {
    591        return getCurrentConfig().getKeys();
    592    }
    593
    594    @Override
    595    protected Iterator<String> getKeysInternal(final String prefix) {
    596        return getCurrentConfig().getKeys(prefix);
    597    }
    598
    599    @Override
    600    public List<Object> getList(final String key) {
    601        return getCurrentConfig().getList(key);
    602    }
    603
    604    @Override
    605    public List<Object> getList(final String key, final List<?> defaultValue) {
    606        return getCurrentConfig().getList(key, defaultValue);
    607    }
    608
    609    @Override
    610    public long getLong(final String key) {
    611        return getCurrentConfig().getLong(key);
    612    }
    613
    614    @Override
    615    public long getLong(final String key, final long defaultValue) {
    616        return getCurrentConfig().getLong(key, defaultValue);
    617    }
    618
    619    @Override
    620    public Long getLong(final String key, final Long defaultValue) {
    621        return getCurrentConfig().getLong(key, defaultValue);
    622    }
    623
    624    @Override
    625    protected int getMaxIndexInternal(final String key) {
    626        return getCurrentConfig().getMaxIndex(key);
    627    }
    628
    629    /**
    630     * Gets the node combiner that is used for creating the combined node structure.
    631     *
    632     * @return the node combiner
    633     */
    634    @Override
    635    public NodeCombiner getNodeCombiner() {
    636        return nodeCombiner;
    637    }
    638
    639    /**
    640     * Gets the number of configurations that are contained in this combined configuration.
    641     *
    642     * @return the number of contained configurations
    643     */
    644    @Override
    645    public int getNumberOfConfigurations() {
    646        beginRead(false);
    647        try {
    648            return configurations.size();
    649        } finally {
    650            endRead();
    651        }
    652    }
    653
    654    @Override
    655    public Properties getProperties(final String key) {
    656        return getCurrentConfig().getProperties(key);
    657    }
    658
    659    @Override
    660    protected Object getPropertyInternal(final String key) {
    661        return getCurrentConfig().getProperty(key);
    662    }
    663
    664    @Override
    665    public short getShort(final String key) {
    666        return getCurrentConfig().getShort(key);
    667    }
    668
    669    @Override
    670    public short getShort(final String key, final short defaultValue) {
    671        return getCurrentConfig().getShort(key, defaultValue);
    672    }
    673
    674    @Override
    675    public Short getShort(final String key, final Short defaultValue) {
    676        return getCurrentConfig().getShort(key, defaultValue);
    677    }
    678
    679    /**
    680     * Gets the configuration source, in which the specified key is defined. This method will determine the configuration
    681     * node that is identified by the given key. The following constellations are possible:
    682     * <ul>
    683     * <li>If no node object is found for this key, <b>null</b> is returned.</li>
    684     * <li>If the key maps to multiple nodes belonging to different configuration sources, a
    685     * {@code IllegalArgumentException} is thrown (in this case no unique source can be determined).</li>
    686     * <li>If exactly one node is found for the key, the (child) configuration object, to which the node belongs is
    687     * determined and returned.</li>
    688     * <li>For keys that have been added directly to this combined configuration and that do not belong to the namespaces
    689     * defined by existing child configurations this configuration will be returned.</li>
    690     * </ul>
    691     *
    692     * @param key the key of a configuration property
    693     * @return the configuration, to which this property belongs or <b>null</b> if the key cannot be resolved
    694     * @throws IllegalArgumentException if the key maps to multiple properties and the source cannot be determined, or if
    695     *         the key is <b>null</b>
    696     */
    697    @Override
    698    public Configuration getSource(final String key) {
    699        if (key == null) {
    700            throw new IllegalArgumentException("Key must not be null!");
    701        }
    702        return getCurrentConfig().getSource(key);
    703    }
    704
    705    @Override
    706    public String getString(final String key) {
    707        return getCurrentConfig().getString(key);
    708    }
    709
    710    @Override
    711    public String getString(final String key, final String defaultValue) {
    712        return getCurrentConfig().getString(key, defaultValue);
    713    }
    714
    715    @Override
    716    public String[] getStringArray(final String key) {
    717        return getCurrentConfig().getStringArray(key);
    718    }
    719
    720    /**
    721     * Initializes a newly created child configuration. This method copies a bunch of settings from this instance to the
    722     * child configuration.
    723     *
    724     * @param config the child configuration to be initialized
    725     */
    726    private void initChildConfiguration(final CombinedConfiguration config) {
    727        if (loggerName != null) {
    728            config.setLogger(new ConfigurationLogger(loggerName));
    729        }
    730        config.setExpressionEngine(getExpressionEngine());
    731        config.setConversionExpressionEngine(getConversionExpressionEngine());
    732        config.setListDelimiterHandler(getListDelimiterHandler());
    733        copyEventListeners(config);
    734        configurations.forEach(data -> config.addConfiguration(data.getConfiguration(), data.getName(), data.getAt()));
    735        config.setSynchronizer(getSynchronizer());
    736    }
    737
    738    /**
    739     * Creates a {@code ConfigurationInterpolator} instance for performing local variable substitutions. This implementation
    740     * returns an object which shares the prefix lookups from this configuration's {@code ConfigurationInterpolator}, but
    741     * does not define any other lookups.
    742     *
    743     * @return the {@code ConfigurationInterpolator}
    744     */
    745    private ConfigurationInterpolator initLocalInterpolator() {
    746        return new ConfigurationInterpolator() {
    747            @Override
    748            protected Lookup fetchLookupForPrefix(final String prefix) {
    749                return nullSafeLookup(getInterpolator().getLookups().get(prefix));
    750            }
    751        };
    752    }
    753
    754    @Override
    755    public Configuration interpolatedConfiguration() {
    756        return getCurrentConfig().interpolatedConfiguration();
    757    }
    758
    759    /**
    760     * Invalidates the current combined configuration. This means that the next time a property is accessed the combined
    761     * node structure must be re-constructed. Invalidation of a combined configuration also means that an event of type
    762     * {@code EVENT_COMBINED_INVALIDATE} is fired. Note that while other events most times appear twice (once before and
    763     * once after an update), this event is only fired once (after update).
    764     */
    765    @Override
    766    public void invalidate() {
    767        getCurrentConfig().invalidate();
    768    }
    769
    770    public void invalidateAll() {
    771        configs.values().forEach(CombinedConfiguration::invalidate);
    772    }
    773
    774    @Override
    775    protected boolean isEmptyInternal() {
    776        return getCurrentConfig().isEmpty();
    777    }
    778
    779    /**
    780     * Decrements the lock count of the current configuration holder. If it reaches 0, the current configuration is removed.
    781     * (It is then reevaluated when the next operation starts.)
    782     */
    783    private void releaseLock() {
    784        final CurrentConfigHolder cch = CURRENT_CONFIG.get();
    785        assert cch != null : "No current configuration!";
    786        if (cch.decrementLockCountAndCheckRelease()) {
    787            CURRENT_CONFIG.remove();
    788        }
    789    }
    790
    791    /**
    792     * Removes the specified configuration from this combined configuration.
    793     *
    794     * @param config the configuration to be removed
    795     * @return a flag whether this configuration was found and could be removed
    796     */
    797    @Override
    798    public boolean removeConfiguration(final Configuration config) {
    799        beginWrite(false);
    800        try {
    801            for (int index = 0; index < getNumberOfConfigurations(); index++) {
    802                if (configurations.get(index).getConfiguration() == config) {
    803                    removeConfigurationAt(index);
    804                    return true;
    805                }
    806            }
    807
    808            return false;
    809        } finally {
    810            endWrite();
    811        }
    812    }
    813
    814    /**
    815     * Removes the configuration with the specified name.
    816     *
    817     * @param name the name of the configuration to be removed
    818     * @return the removed configuration (<b>null</b> if this configuration was not found)
    819     */
    820    @Override
    821    public Configuration removeConfiguration(final String name) {
    822        final Configuration conf = getConfiguration(name);
    823        if (conf != null) {
    824            removeConfiguration(conf);
    825        }
    826        return conf;
    827    }
    828
    829    /**
    830     * Removes the configuration at the specified index.
    831     *
    832     * @param index the index
    833     * @return the removed configuration
    834     */
    835    @Override
    836    public Configuration removeConfigurationAt(final int index) {
    837        beginWrite(false);
    838        try {
    839            final ConfigData cd = configurations.remove(index);
    840            if (cd.getName() != null) {
    841                namedConfigurations.remove(cd.getName());
    842            }
    843            return cd.getConfiguration();
    844        } finally {
    845            endWrite();
    846        }
    847    }
    848
    849    @Override
    850    public <T extends Event> boolean removeEventListener(final EventType<T> eventType, final EventListener<? super T> listener) {
    851        configs.values().forEach(cc -> cc.removeEventListener(eventType, listener));
    852        return super.removeEventListener(eventType, listener);
    853    }
    854
    855    public void setKeyPattern(final String pattern) {
    856        this.keyPattern = pattern;
    857    }
    858
    859    /**
    860     * Sets the name of the Logger to use on each CombinedConfiguration.
    861     *
    862     * @param name The Logger name.
    863     */
    864    public void setLoggerName(final String name) {
    865        this.loggerName = name;
    866    }
    867
    868    /**
    869     * Sets the node combiner. This object will be used when the combined node structure is to be constructed. It must not
    870     * be <b>null</b>, otherwise an {@code IllegalArgumentException} exception is thrown. Changing the node combiner causes
    871     * an invalidation of this combined configuration, so that the new combiner immediately takes effect.
    872     *
    873     * @param nodeCombiner the node combiner
    874     */
    875    @Override
    876    public void setNodeCombiner(final NodeCombiner nodeCombiner) {
    877        if (nodeCombiner == null) {
    878            throw new IllegalArgumentException("Node combiner must not be null!");
    879        }
    880        this.nodeCombiner = nodeCombiner;
    881        invalidateAll();
    882    }
    883
    884    @Override
    885    protected void setPropertyInternal(final String key, final Object value) {
    886        getCurrentConfig().setProperty(key, value);
    887    }
    888
    889    @Override
    890    protected int sizeInternal() {
    891        return getCurrentConfig().size();
    892    }
    893
    894    @Override
    895    public Configuration subset(final String prefix) {
    896        return getCurrentConfig().subset(prefix);
    897    }
    898}