    001/*
    002 * Licensed to the Apache Software Foundation (ASF) under one or more
    003 * contributor license agreements.  See the NOTICE file distributed with
    004 * this work for additional information regarding copyright ownership.
    005 * The ASF licenses this file to You under the Apache License, Version 2.0
    006 * (the "License"); you may not use this file except in compliance with
    007 * the License.  You may obtain a copy of the License at
    008 *
    009 *     http://www.apache.org/licenses/LICENSE-2.0
    010 *
    011 * Unless required by applicable law or agreed to in writing, software
    012 * distributed under the License is distributed on an "AS IS" BASIS,
    013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014 * See the License for the specific language governing permissions and
    015 * limitations under the License.
    016 */
    017package org.apache.commons.configuration2;
    018
    019import java.math.BigDecimal;
    020import java.math.BigInteger;
    021import java.util.ArrayList;
    022import java.util.Collection;
    023import java.util.Collections;
    024import java.util.HashMap;
    025import java.util.Iterator;
    026import java.util.List;
    027import java.util.Map;
    028import java.util.Properties;
    029import java.util.Set;
    030import java.util.concurrent.ConcurrentHashMap;
    031import java.util.concurrent.ConcurrentMap;
    032
    033import org.apache.commons.configuration2.event.BaseEventSource;
    034import org.apache.commons.configuration2.event.Event;
    035import org.apache.commons.configuration2.event.EventListener;
    036import org.apache.commons.configuration2.event.EventType;
    037import org.apache.commons.configuration2.interpol.ConfigurationInterpolator;
    038import org.apache.commons.configuration2.interpol.Lookup;
    039import org.apache.commons.configuration2.io.ConfigurationLogger;
    040import org.apache.commons.configuration2.tree.ImmutableNode;
    041import org.apache.commons.configuration2.tree.NodeCombiner;
    042
    043/**
    044 * <p>
    045 * DynamicCombinedConfiguration allows a set of CombinedConfigurations to be used.
    046 * </p>
    047 * <p>
    048 * Each CombinedConfiguration is referenced by a key that is dynamically constructed from a key pattern on each call.
    049 * The key pattern will be resolved using the configured ConfigurationInterpolator.
    050 * </p>
    051 * <p>
    052 * This Configuration implementation uses the configured {@code Synchronizer} to guard itself against concurrent access.
    053 * If there are multiple threads accessing an instance concurrently, a fully functional {@code Synchronizer}
    054 * implementation (e.g. {@code ReadWriteSynchronizer}) has to be used to ensure consistency and to avoid exceptions. The
    055 * {@code Synchronizer} assigned to an instance is also passed to child configuration objects when they are created.
    056 * </p>
    057 *
    058 * @since 1.6
    059 */
    060public class DynamicCombinedConfiguration extends CombinedConfiguration {
    061    /**
    062     * 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     * Gets 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     * Gets 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     * Gets 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     * Gets 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     * Gets 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    protected void addNodesInternal(final String key, final Collection<? extends ImmutableNode> nodes) {
    516        this.getCurrentConfig().addNodes(key, nodes);
    517    }
    518
    519    @Override
    520    public HierarchicalConfiguration<ImmutableNode> configurationAt(final String key, final boolean supportUpdates) {
    521        return this.getCurrentConfig().configurationAt(key, supportUpdates);
    522    }
    523
    524    @Override
    525    public HierarchicalConfiguration<ImmutableNode> configurationAt(final String key) {
    526        return this.getCurrentConfig().configurationAt(key);
    527    }
    528
    529    @Override
    530    public List<HierarchicalConfiguration<ImmutableNode>> configurationsAt(final String key) {
    531        return this.getCurrentConfig().configurationsAt(key);
    532    }
    533
    534    @Override
    535    protected Object clearTreeInternal(final String key) {
    536        this.getCurrentConfig().clearTree(key);
    537        return Collections.emptyList();
    538    }
    539
    540    @Override
    541    protected int getMaxIndexInternal(final String key) {
    542        return this.getCurrentConfig().getMaxIndex(key);
    543    }
    544
    545    @Override
    546    public Configuration interpolatedConfiguration() {
    547        return this.getCurrentConfig().interpolatedConfiguration();
    548    }
    549
    550    /**
    551     * Gets the configuration source, in which the specified key is defined. This method will determine the configuration
    552     * node that is identified by the given key. The following constellations are possible:
    553     * <ul>
    554     * <li>If no node object is found for this key, <b>null</b> is returned.</li>
    555     * <li>If the key maps to multiple nodes belonging to different configuration sources, a
    556     * {@code IllegalArgumentException} is thrown (in this case no unique source can be determined).</li>
    557     * <li>If exactly one node is found for the key, the (child) configuration object, to which the node belongs is
    558     * determined and returned.</li>
    559     * <li>For keys that have been added directly to this combined configuration and that do not belong to the namespaces
    560     * defined by existing child configurations this configuration will be returned.</li>
    561     * </ul>
    562     *
    563     * @param key the key of a configuration property
    564     * @return the configuration, to which this property belongs or <b>null</b> if the key cannot be resolved
    565     * @throws IllegalArgumentException if the key maps to multiple properties and the source cannot be determined, or if
    566     *         the key is <b>null</b>
    567     */
    568    @Override
    569    public Configuration getSource(final String key) {
    570        if (key == null) {
    571            throw new IllegalArgumentException("Key must not be null!");
    572        }
    573        return getCurrentConfig().getSource(key);
    574    }
    575
    576    @Override
    577    public void clearEventListeners() {
    578        configs.values().forEach(CombinedConfiguration::clearEventListeners);
    579        super.clearEventListeners();
    580    }
    581
    582    @Override
    583    public <T extends Event> void addEventListener(final EventType<T> eventType, final EventListener<? super T> listener) {
    584        configs.values().forEach(cc -> cc.addEventListener(eventType, listener));
    585        super.addEventListener(eventType, listener);
    586    }
    587
    588    @Override
    589    public <T extends Event> boolean removeEventListener(final EventType<T> eventType, final EventListener<? super T> listener) {
    590        configs.values().forEach(cc -> cc.removeEventListener(eventType, listener));
    591        return super.removeEventListener(eventType, listener);
    592    }
    593
    594    @Override
    595    public void clearErrorListeners() {
    596        configs.values().forEach(BaseEventSource::clearErrorListeners);
    597        super.clearErrorListeners();
    598    }
    599
    600    /**
    601     * Invalidates the current combined configuration. This means that the next time a property is accessed the combined
    602     * node structure must be re-constructed. Invalidation of a combined configuration also means that an event of type
    603     * {@code EVENT_COMBINED_INVALIDATE} is fired. Note that while other events most times appear twice (once before and
    604     * once after an update), this event is only fired once (after update).
    605     */
    606    @Override
    607    public void invalidate() {
    608        getCurrentConfig().invalidate();
    609    }
    610
    611    public void invalidateAll() {
    612        configs.values().forEach(CombinedConfiguration::invalidate);
    613    }
    614
    615    /**
    616     * {@inheritDoc} This implementation ensures that the current configuration is initialized. The lock counter is
    617     * increased.
    618     */
    619    @Override
    620    protected void beginRead(final boolean optimize) {
    621        final CurrentConfigHolder cch = ensureCurrentConfiguration();
    622        cch.incrementLockCount();
    623        if (!optimize && cch.getCurrentConfiguration() == null) {
    624            // delegate to beginWrite() which creates the child configuration
    625            beginWrite(false);
    626            endWrite();
    627        }
    628
    629        // This actually uses our own synchronizer
    630        cch.getCurrentConfiguration().beginRead(optimize);
    631    }
    632
    633    /**
    634     * {@inheritDoc} This implementation ensures that the current configuration is initialized. If necessary, a new child
    635     * configuration instance is created.
    636     */
    637    @Override
    638    protected void beginWrite(final boolean optimize) {
    639        final CurrentConfigHolder cch = ensureCurrentConfiguration();
    640        cch.incrementLockCount();
    641
    642        super.beginWrite(optimize);
    643        if (!optimize && cch.getCurrentConfiguration() == null) {
    644            cch.setCurrentConfiguration(createChildConfiguration());
    645            configs.put(cch.getKey(), cch.getCurrentConfiguration());
    646            initChildConfiguration(cch.getCurrentConfiguration());
    647        }
    648    }
    649
    650    /**
    651     * {@inheritDoc} This implementation clears the current configuration if necessary.
    652     */
    653    @Override
    654    protected void endRead() {
    655        CURRENT_CONFIG.get().getCurrentConfiguration().endRead();
    656        releaseLock();
    657    }
    658
    659    /**
    660     * {@inheritDoc} This implementation clears the current configuration if necessary.
    661     */
    662    @Override
    663    protected void endWrite() {
    664        super.endWrite();
    665        releaseLock();
    666    }
    667
    668    /**
    669     * Decrements the lock count of the current configuration holder. If it reaches 0, the current configuration is removed.
    670     * (It is then reevaluated when the next operation starts.)
    671     */
    672    private void releaseLock() {
    673        final CurrentConfigHolder cch = CURRENT_CONFIG.get();
    674        assert cch != null : "No current configuration!";
    675        if (cch.decrementLockCountAndCheckRelease()) {
    676            CURRENT_CONFIG.remove();
    677        }
    678    }
    679
    680    /**
    681     * Gets the current configuration. This configuration was initialized at the beginning of an operation and stored in
    682     * a thread-local variable. Some methods of this class call this method directly without requesting a lock before. To
    683     * deal with this, we always request an additional read lock.
    684     *
    685     * @return the current configuration
    686     */
    687    private CombinedConfiguration getCurrentConfig() {
    688        CombinedConfiguration config;
    689        String key;
    690        beginRead(false);
    691        try {
    692            config = CURRENT_CONFIG.get().getCurrentConfiguration();
    693            key = CURRENT_CONFIG.get().getKey();
    694        } finally {
    695            endRead();
    696        }
    697
    698        if (getLogger().isDebugEnabled()) {
    699            getLogger().debug("Returning config for " + key + ": " + config);
    700        }
    701        return config;
    702    }
    703
    704    /**
    705     * Creates a new, uninitialized child configuration.
    706     *
    707     * @return the new child configuration
    708     */
    709    private CombinedConfiguration createChildConfiguration() {
    710        return new CombinedConfiguration(getNodeCombiner());
    711    }
    712
    713    /**
    714     * Initializes a newly created child configuration. This method copies a bunch of settings from this instance to the
    715     * child configuration.
    716     *
    717     * @param config the child configuration to be initialized
    718     */
    719    private void initChildConfiguration(final CombinedConfiguration config) {
    720        if (loggerName != null) {
    721            config.setLogger(new ConfigurationLogger(loggerName));
    722        }
    723        config.setExpressionEngine(this.getExpressionEngine());
    724        config.setConversionExpressionEngine(getConversionExpressionEngine());
    725        config.setListDelimiterHandler(getListDelimiterHandler());
    726        copyEventListeners(config);
    727        configurations.forEach(data -> config.addConfiguration(data.getConfiguration(), data.getName(), data.getAt()));
    728        config.setSynchronizer(getSynchronizer());
    729    }
    730
    731    /**
    732     * Creates a {@code ConfigurationInterpolator} instance for performing local variable substitutions. This implementation
    733     * returns an object which shares the prefix lookups from this configuration's {@code ConfigurationInterpolator}, but
    734     * does not define any other lookups.
    735     *
    736     * @return the {@code ConfigurationInterpolator}
    737     */
    738    private ConfigurationInterpolator initLocalInterpolator() {
    739        return new ConfigurationInterpolator() {
    740            @Override
    741            protected Lookup fetchLookupForPrefix(final String prefix) {
    742                return ConfigurationInterpolator.nullSafeLookup(getInterpolator().getLookups().get(prefix));
    743            }
    744        };
    745    }
    746
    747    /**
    748     * Checks whether the current configuration is set. If not, a {@code CurrentConfigHolder} is now created and
    749     * initialized, and associated with the current thread. The member for the current configuration is undefined if for the
    750     * current key no configuration exists yet.
    751     *
    752     * @return the {@code CurrentConfigHolder} instance for the current thread
    753     */
    754    private CurrentConfigHolder ensureCurrentConfiguration() {
    755        CurrentConfigHolder cch = CURRENT_CONFIG.get();
    756        if (cch == null) {
    757            final String key = String.valueOf(localSubst.interpolate(keyPattern));
    758            cch = new CurrentConfigHolder(key);
    759            cch.setCurrentConfiguration(configs.get(key));
    760            CURRENT_CONFIG.set(cch);
    761        }
    762        return cch;
    763    }
    764
    765    /**
    766     * Internal class that identifies each Configuration.
    767     */
    768    static class ConfigData {
    769        /** Stores a reference to the configuration. */
    770        private final Configuration configuration;
    771
    772        /** Stores the name under which the configuration is stored. */
    773        private final String name;
    774
    775        /** Stores the at string. */
    776        private final String at;
    777
    778        /**
    779         * Creates a new instance of {@code ConfigData} and initializes it.
    780         *
    781         * @param config the configuration
    782         * @param n the name
    783         * @param at the at position
    784         */
    785        public ConfigData(final Configuration config, final String n, final String at) {
    786            configuration = config;
    787            name = n;
    788            this.at = at;
    789        }
    790
    791        /**
    792         * Gets the stored configuration.
    793         *
    794         * @return the configuration
    795         */
    796        public Configuration getConfiguration() {
    797            return configuration;
    798        }
    799
    800        /**
    801         * Gets the configuration's name.
    802         *
    803         * @return the name
    804         */
    805        public String getName() {
    806            return name;
    807        }
    808
    809        /**
    810         * Gets the at position of this configuration.
    811         *
    812         * @return the at position
    813         */
    814        public String getAt() {
    815            return at;
    816        }
    817
    818    }
    819
    820    /**
    821     * A simple data class holding information about the current configuration while an operation for a thread is processed.
    822     */
    823    private static class CurrentConfigHolder {
    824        /** Stores the current configuration of the current thread. */
    825        private CombinedConfiguration currentConfiguration;
    826
    827        /**
    828         * Stores the key of the configuration evaluated for the current thread at the beginning of an operation.
    829         */
    830        private final String key;
    831
    832        /** A counter for reentrant locks. */
    833        private int lockCount;
    834
    835        /**
    836         * Creates a new instance of {@code CurrentConfigHolder} and initializes it with the key for the current configuration.
    837         *
    838         * @param curKey the current key
    839         */
    840        public CurrentConfigHolder(final String curKey) {
    841            key = curKey;
    842        }
    843
    844        /**
    845         * Gets the current configuration.
    846         *
    847         * @return the current configuration
    848         */
    849        public CombinedConfiguration getCurrentConfiguration() {
    850            return currentConfiguration;
    851        }
    852
    853        /**
    854         * Sets the current configuration.
    855         *
    856         * @param currentConfiguration the current configuration
    857         */
    858        public void setCurrentConfiguration(final CombinedConfiguration currentConfiguration) {
    859            this.currentConfiguration = currentConfiguration;
    860        }
    861
    862        /**
    863         * Gets the current key.
    864         *
    865         * @return the current key
    866         */
    867        public String getKey() {
    868            return key;
    869        }
    870
    871        /**
    872         * Increments the lock counter.
    873         */
    874        public void incrementLockCount() {
    875            lockCount++;
    876        }
    877
    878        /**
    879         * Decrements the lock counter and checks whether it has reached 0. In this cause, the operation is complete, and the
    880         * lock can be released.
    881         *
    882         * @return <b>true</b> if the lock count reaches 0, <b>false</b> otherwise
    883         */
    884        public boolean decrementLockCountAndCheckRelease() {
    885            return --lockCount == 0;
    886        }
    887    }
    888}