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