    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 (for example {@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 <strong>true</strong> if the lock count reaches 0, <strong>false</strong> 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 <strong>null</strong>, 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 <strong>null</strong>)
    242     * @param name the name of this configuration (can be <strong>null</strong>)
    243     * @param at the position of this configuration in the combined tree (can be <strong>null</strong>)
    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 <strong>null</strong> 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 <strong>null</strong>)
    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    /**
    586     * Gets the key pattern for the CombinedConfiguration map.
    587     *
    588     * @return the key pattern for the CombinedConfiguration map.
    589     */
    590    public String getKeyPattern() {
    591        return this.keyPattern;
    592    }
    593
    594    @Override
    595    protected Iterator<String> getKeysInternal() {
    596        return getCurrentConfig().getKeys();
    597    }
    598
    599    @Override
    600    protected Iterator<String> getKeysInternal(final String prefix) {
    601        return getCurrentConfig().getKeys(prefix);
    602    }
    603
    604    @Override
    605    public List<Object> getList(final String key) {
    606        return getCurrentConfig().getList(key);
    607    }
    608
    609    @Override
    610    public List<Object> getList(final String key, final List<?> defaultValue) {
    611        return getCurrentConfig().getList(key, defaultValue);
    612    }
    613
    614    @Override
    615    public long getLong(final String key) {
    616        return getCurrentConfig().getLong(key);
    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    public Long getLong(final String key, final Long defaultValue) {
    626        return getCurrentConfig().getLong(key, defaultValue);
    627    }
    628
    629    @Override
    630    protected int getMaxIndexInternal(final String key) {
    631        return getCurrentConfig().getMaxIndex(key);
    632    }
    633
    634    /**
    635     * Gets the node combiner that is used for creating the combined node structure.
    636     *
    637     * @return the node combiner
    638     */
    639    @Override
    640    public NodeCombiner getNodeCombiner() {
    641        return nodeCombiner;
    642    }
    643
    644    /**
    645     * Gets the number of configurations that are contained in this combined configuration.
    646     *
    647     * @return the number of contained configurations
    648     */
    649    @Override
    650    public int getNumberOfConfigurations() {
    651        beginRead(false);
    652        try {
    653            return configurations.size();
    654        } finally {
    655            endRead();
    656        }
    657    }
    658
    659    @Override
    660    public Properties getProperties(final String key) {
    661        return getCurrentConfig().getProperties(key);
    662    }
    663
    664    @Override
    665    protected Object getPropertyInternal(final String key) {
    666        return getCurrentConfig().getProperty(key);
    667    }
    668
    669    @Override
    670    public short getShort(final String key) {
    671        return getCurrentConfig().getShort(key);
    672    }
    673
    674    @Override
    675    public short getShort(final String key, final short defaultValue) {
    676        return getCurrentConfig().getShort(key, defaultValue);
    677    }
    678
    679    @Override
    680    public Short getShort(final String key, final Short defaultValue) {
    681        return getCurrentConfig().getShort(key, defaultValue);
    682    }
    683
    684    /**
    685     * Gets the configuration source, in which the specified key is defined. This method will determine the configuration
    686     * node that is identified by the given key. The following constellations are possible:
    687     * <ul>
    688     * <li>If no node object is found for this key, <strong>null</strong> is returned.</li>
    689     * <li>If the key maps to multiple nodes belonging to different configuration sources, a
    690     * {@code IllegalArgumentException} is thrown (in this case no unique source can be determined).</li>
    691     * <li>If exactly one node is found for the key, the (child) configuration object, to which the node belongs is
    692     * determined and returned.</li>
    693     * <li>For keys that have been added directly to this combined configuration and that do not belong to the namespaces
    694     * defined by existing child configurations this configuration will be returned.</li>
    695     * </ul>
    696     *
    697     * @param key the key of a configuration property
    698     * @return the configuration, to which this property belongs or <strong>null</strong> if the key cannot be resolved
    699     * @throws IllegalArgumentException if the key maps to multiple properties and the source cannot be determined, or if
    700     *         the key is <strong>null</strong>
    701     */
    702    @Override
    703    public Configuration getSource(final String key) {
    704        if (key == null) {
    705            throw new IllegalArgumentException("Key must not be null!");
    706        }
    707        return getCurrentConfig().getSource(key);
    708    }
    709
    710    @Override
    711    public String getString(final String key) {
    712        return getCurrentConfig().getString(key);
    713    }
    714
    715    @Override
    716    public String getString(final String key, final String defaultValue) {
    717        return getCurrentConfig().getString(key, defaultValue);
    718    }
    719
    720    @Override
    721    public String[] getStringArray(final String key) {
    722        return getCurrentConfig().getStringArray(key);
    723    }
    724
    725    /**
    726     * Initializes a newly created child configuration. This method copies a bunch of settings from this instance to the
    727     * child configuration.
    728     *
    729     * @param config the child configuration to be initialized
    730     */
    731    private void initChildConfiguration(final CombinedConfiguration config) {
    732        if (loggerName != null) {
    733            config.setLogger(new ConfigurationLogger(loggerName));
    734        }
    735        config.setExpressionEngine(getExpressionEngine());
    736        config.setConversionExpressionEngine(getConversionExpressionEngine());
    737        config.setListDelimiterHandler(getListDelimiterHandler());
    738        copyEventListeners(config);
    739        configurations.forEach(data -> config.addConfiguration(data.getConfiguration(), data.getName(), data.getAt()));
    740        config.setSynchronizer(getSynchronizer());
    741    }
    742
    743    /**
    744     * Creates a {@code ConfigurationInterpolator} instance for performing local variable substitutions. This implementation
    745     * returns an object which shares the prefix lookups from this configuration's {@code ConfigurationInterpolator}, but
    746     * does not define any other lookups.
    747     *
    748     * @return the {@code ConfigurationInterpolator}
    749     */
    750    private ConfigurationInterpolator initLocalInterpolator() {
    751        return new ConfigurationInterpolator() {
    752            @Override
    753            protected Lookup fetchLookupForPrefix(final String prefix) {
    754                return nullSafeLookup(getInterpolator().getLookups().get(prefix));
    755            }
    756        };
    757    }
    758
    759    @Override
    760    public Configuration interpolatedConfiguration() {
    761        return getCurrentConfig().interpolatedConfiguration();
    762    }
    763
    764    /**
    765     * Invalidates the current combined configuration. This means that the next time a property is accessed the combined
    766     * node structure must be re-constructed. Invalidation of a combined configuration also means that an event of type
    767     * {@code EVENT_COMBINED_INVALIDATE} is fired. Note that while other events most times appear twice (once before and
    768     * once after an update), this event is only fired once (after update).
    769     */
    770    @Override
    771    public void invalidate() {
    772        getCurrentConfig().invalidate();
    773    }
    774
    775    /**
    776     * Invalidates all CombinedConfigurations.
    777     */
    778    public void invalidateAll() {
    779        configs.values().forEach(CombinedConfiguration::invalidate);
    780    }
    781
    782    @Override
    783    protected boolean isEmptyInternal() {
    784        return getCurrentConfig().isEmpty();
    785    }
    786
    787    /**
    788     * Decrements the lock count of the current configuration holder. If it reaches 0, the current configuration is removed.
    789     * (It is then reevaluated when the next operation starts.)
    790     */
    791    private void releaseLock() {
    792        final CurrentConfigHolder cch = CURRENT_CONFIG.get();
    793        assert cch != null : "No current configuration!";
    794        if (cch.decrementLockCountAndCheckRelease()) {
    795            CURRENT_CONFIG.remove();
    796        }
    797    }
    798
    799    /**
    800     * Removes the specified configuration from this combined configuration.
    801     *
    802     * @param config the configuration to be removed
    803     * @return a flag whether this configuration was found and could be removed
    804     */
    805    @Override
    806    public boolean removeConfiguration(final Configuration config) {
    807        beginWrite(false);
    808        try {
    809            for (int index = 0; index < getNumberOfConfigurations(); index++) {
    810                if (configurations.get(index).getConfiguration() == config) {
    811                    removeConfigurationAt(index);
    812                    return true;
    813                }
    814            }
    815
    816            return false;
    817        } finally {
    818            endWrite();
    819        }
    820    }
    821
    822    /**
    823     * Removes the configuration with the specified name.
    824     *
    825     * @param name the name of the configuration to be removed
    826     * @return the removed configuration (<strong>null</strong> if this configuration was not found)
    827     */
    828    @Override
    829    public Configuration removeConfiguration(final String name) {
    830        final Configuration conf = getConfiguration(name);
    831        if (conf != null) {
    832            removeConfiguration(conf);
    833        }
    834        return conf;
    835    }
    836
    837    /**
    838     * Removes the configuration at the specified index.
    839     *
    840     * @param index the index
    841     * @return the removed configuration
    842     */
    843    @Override
    844    public Configuration removeConfigurationAt(final int index) {
    845        beginWrite(false);
    846        try {
    847            final ConfigData cd = configurations.remove(index);
    848            if (cd.getName() != null) {
    849                namedConfigurations.remove(cd.getName());
    850            }
    851            return cd.getConfiguration();
    852        } finally {
    853            endWrite();
    854        }
    855    }
    856
    857    @Override
    858    public <T extends Event> boolean removeEventListener(final EventType<T> eventType, final EventListener<? super T> listener) {
    859        configs.values().forEach(cc -> cc.removeEventListener(eventType, listener));
    860        return super.removeEventListener(eventType, listener);
    861    }
    862
    863    /**
    864     * Sets the key pattern for the CombinedConfiguration map.
    865     *
    866     * @param pattern the key pattern for the CombinedConfiguration map.
    867     */
    868    public void setKeyPattern(final String pattern) {
    869        this.keyPattern = pattern;
    870    }
    871
    872    /**
    873     * Sets the name of the Logger to use on each CombinedConfiguration.
    874     *
    875     * @param name The Logger name.
    876     */
    877    public void setLoggerName(final String name) {
    878        this.loggerName = name;
    879    }
    880
    881    /**
    882     * Sets the node combiner. This object will be used when the combined node structure is to be constructed. It must not
    883     * be <strong>null</strong>, otherwise an {@code IllegalArgumentException} exception is thrown. Changing the node combiner causes
    884     * an invalidation of this combined configuration, so that the new combiner immediately takes effect.
    885     *
    886     * @param nodeCombiner the node combiner
    887     */
    888    @Override
    889    public void setNodeCombiner(final NodeCombiner nodeCombiner) {
    890        if (nodeCombiner == null) {
    891            throw new IllegalArgumentException("Node combiner must not be null!");
    892        }
    893        this.nodeCombiner = nodeCombiner;
    894        invalidateAll();
    895    }
    896
    897    @Override
    898    protected void setPropertyInternal(final String key, final Object value) {
    899        getCurrentConfig().setProperty(key, value);
    900    }
    901
    902    @Override
    903    protected int sizeInternal() {
    904        return getCurrentConfig().size();
    905    }
    906
    907    @Override
    908    public Configuration subset(final String prefix) {
    909        return getCurrentConfig().subset(prefix);
    910    }
    911}