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