/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.commons.configuration2;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import org.apache.commons.configuration2.event.Event;
import org.apache.commons.configuration2.event.EventListener;
import org.apache.commons.configuration2.event.EventType;
import org.apache.commons.configuration2.interpol.ConfigurationInterpolator;
import org.apache.commons.configuration2.interpol.Lookup;
import org.apache.commons.configuration2.tree.ExpressionEngine;
import org.apache.commons.configuration2.tree.ImmutableNode;
import org.apache.commons.configuration2.tree.NodeCombiner;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * <p>
 * DynamicCombinedConfiguration allows a set of CombinedConfigurations to be
 * used.
 * </p>
 * <p>
 * Each CombinedConfiguration is referenced by a key that is dynamically
 * constructed from a key pattern on each call. The key pattern will be resolved
 * using the configured ConfigurationInterpolator.
 * </p>
 * <p>
 * This Configuration implementation uses the configured {@code Synchronizer} to
 * guard itself against concurrent access. If there are multiple threads
 * accessing an instance concurrently, a fully functional {@code Synchronizer}
 * implementation (e.g. {@code ReadWriteSynchronizer}) has to be used to ensure
 * consistency and to avoid exceptions. The {@code Synchronizer} assigned to an
 * instance is also passed to child configuration objects when they are created.
 * </p>
 *
 * @since 1.6
 * @version $Id: DynamicCombinedConfiguration.java 1643923 2014-12-08 21:05:23Z oheger $
 */
public class DynamicCombinedConfiguration extends CombinedConfiguration
{
    /**
     * Stores the current configuration for each involved thread. This value is
     * set at the beginning of an operation and removed at the end.
     */
    private static final ThreadLocal<CurrentConfigHolder> currentConfig =
            new ThreadLocal<CurrentConfigHolder>();

    /** The CombinedConfigurations */
    private final ConcurrentMap<String, CombinedConfiguration> configs =
            new ConcurrentHashMap<String, CombinedConfiguration>();

    /** Stores a list with the contained configurations. */
    private final List<ConfigData> configurations = new ArrayList<ConfigData>();

    /** Stores a map with the named configurations. */
    private final Map<String, Configuration> namedConfigurations =
            new HashMap<String, Configuration>();

    /** The key pattern for the CombinedConfiguration map */
    private String keyPattern;

    /** Stores the combiner. */
    private NodeCombiner nodeCombiner;

    /** The name of the logger to use for each CombinedConfiguration */
    private String loggerName = DynamicCombinedConfiguration.class.getName();

    /** The object for handling variable substitution in key patterns. */
    private final ConfigurationInterpolator localSubst;

    /**
     * Creates a new instance of {@code DynamicCombinedConfiguration} and
     * initializes the combiner to be used.
     *
     * @param comb the node combiner (can be <b>null</b>, then a union combiner
     * is used as default)
     */
    public DynamicCombinedConfiguration(NodeCombiner comb)
    {
        super();
        setNodeCombiner(comb);
        setLogger(LogFactory.getLog(DynamicCombinedConfiguration.class));
        localSubst = initLocalInterpolator();
    }

    /**
     * Creates a new instance of {@code DynamicCombinedConfiguration} that uses
     * a union combiner.
     *
     * @see org.apache.commons.configuration2.tree.UnionCombiner
     */
    public DynamicCombinedConfiguration()
    {
        super();
        setLogger(LogFactory.getLog(DynamicCombinedConfiguration.class));
        localSubst = initLocalInterpolator();
    }

    public void setKeyPattern(String pattern)
    {
        this.keyPattern = pattern;
    }

    public String getKeyPattern()
    {
        return this.keyPattern;
    }

    /**
     * Set the name of the Logger to use on each CombinedConfiguration.
     * @param name The Logger name.
     */
    public void setLoggerName(String name)
    {
        this.loggerName = name;
    }

    /**
     * Returns the node combiner that is used for creating the combined node
     * structure.
     *
     * @return the node combiner
     */
    @Override
    public NodeCombiner getNodeCombiner()
    {
        return nodeCombiner;
    }

    /**
     * Sets the node combiner. This object will be used when the combined node
     * structure is to be constructed. It must not be <b>null</b>, otherwise an
     * {@code IllegalArgumentException} exception is thrown. Changing the
     * node combiner causes an invalidation of this combined configuration, so
     * that the new combiner immediately takes effect.
     *
     * @param nodeCombiner the node combiner
     */
    @Override
    public void setNodeCombiner(NodeCombiner nodeCombiner)
    {
        if (nodeCombiner == null)
        {
            throw new IllegalArgumentException(
                    "Node combiner must not be null!");
        }
        this.nodeCombiner = nodeCombiner;
        invalidateAll();
    }
    /**
     * Adds a new configuration to this combined configuration. It is possible
     * (but not mandatory) to give the new configuration a name. This name must
     * be unique, otherwise a {@code ConfigurationRuntimeException} will
     * be thrown. With the optional {@code at} argument you can specify
     * where in the resulting node structure the content of the added
     * configuration should appear. This is a string that uses dots as property
     * delimiters (independent on the current expression engine). For instance
     * if you pass in the string {@code "database.tables"},
     * all properties of the added configuration will occur in this branch.
     *
     * @param config the configuration to add (must not be <b>null</b>)
     * @param name the name of this configuration (can be <b>null</b>)
     * @param at the position of this configuration in the combined tree (can be
     * <b>null</b>)
     */
    @Override
    public void addConfiguration(Configuration config, String name,
            String at)
    {
        beginWrite(true);
        try
        {
            ConfigData cd = new ConfigData(config, name, at);
            configurations.add(cd);
            if (name != null)
            {
                namedConfigurations.put(name, config);
            }

            // clear cache of all child configurations
            configs.clear();
        }
        finally
        {
            endWrite();
        }
    }
       /**
     * Returns the number of configurations that are contained in this combined
     * configuration.
     *
     * @return the number of contained configurations
     */
    @Override
    public int getNumberOfConfigurations()
    {
        beginRead(false);
        try
        {
            return configurations.size();
        }
        finally
        {
            endRead();
        }
    }

    /**
     * Returns the configuration at the specified index. The contained
     * configurations are numbered in the order they were added to this combined
     * configuration. The index of the first configuration is 0.
     *
     * @param index the index
     * @return the configuration at this index
     */
    @Override
    public Configuration getConfiguration(int index)
    {
        beginRead(false);
        try
        {
            ConfigData cd = configurations.get(index);
            return cd.getConfiguration();
        }
        finally
        {
            endRead();
        }
    }

    /**
     * Returns the configuration with the given name. This can be <b>null</b>
     * if no such configuration exists.
     *
     * @param name the name of the configuration
     * @return the configuration with this name
     */
    @Override
    public Configuration getConfiguration(String name)
    {
        beginRead(false);
        try
        {
            return namedConfigurations.get(name);
        }
        finally
        {
            endRead();
        }
    }

    /**
     * Returns a set with the names of all configurations contained in this
     * combined configuration. Of course here are only these configurations
     * listed, for which a name was specified when they were added.
     *
     * @return a set with the names of the contained configurations (never
     * <b>null</b>)
     */
    @Override
    public Set<String> getConfigurationNames()
    {
        beginRead(false);
        try
        {
            return namedConfigurations.keySet();
        }
        finally
        {
            endRead();
        }
    }

    /**
     * Removes the configuration with the specified name.
     *
     * @param name the name of the configuration to be removed
     * @return the removed configuration (<b>null</b> if this configuration
     * was not found)
     */
    @Override
    public Configuration removeConfiguration(String name)
    {
        Configuration conf = getConfiguration(name);
        if (conf != null)
        {
            removeConfiguration(conf);
        }
        return conf;
    }

    /**
     * Removes the specified configuration from this combined configuration.
     *
     * @param config the configuration to be removed
     * @return a flag whether this configuration was found and could be removed
     */
    @Override
    public boolean removeConfiguration(Configuration config)
    {
        beginWrite(false);
        try
        {
            for (int index = 0; index < getNumberOfConfigurations(); index++)
            {
                if (configurations.get(index).getConfiguration() == config)
                {
                    removeConfigurationAt(index);
                    return true;
                }
            }

            return false;
        }
        finally
        {
            endWrite();
        }
    }

    /**
     * Removes the configuration at the specified index.
     *
     * @param index the index
     * @return the removed configuration
     */
    @Override
    public Configuration removeConfigurationAt(int index)
    {
        beginWrite(false);
        try
        {
            ConfigData cd = configurations.remove(index);
            if (cd.getName() != null)
            {
                namedConfigurations.remove(cd.getName());
            }
            return cd.getConfiguration();
        }
        finally
        {
            endWrite();
        }
    }

    @Override
    protected void addPropertyInternal(String key, Object value)
    {
        this.getCurrentConfig().addProperty(key, value);
    }

    @Override
    protected void clearInternal()
    {
        if (configs != null)
        {
            this.getCurrentConfig().clear();
        }
    }

    @Override
    protected void clearPropertyDirect(String key)
    {
        this.getCurrentConfig().clearProperty(key);
    }

    @Override
    protected boolean containsKeyInternal(String key)
    {
        return this.getCurrentConfig().containsKey(key);
    }

    @Override
    public BigDecimal getBigDecimal(String key, BigDecimal defaultValue)
    {
        return this.getCurrentConfig().getBigDecimal(key, defaultValue);
    }

    @Override
    public BigDecimal getBigDecimal(String key)
    {
        return this.getCurrentConfig().getBigDecimal(key);
    }

    @Override
    public BigInteger getBigInteger(String key, BigInteger defaultValue)
    {
        return this.getCurrentConfig().getBigInteger(key, defaultValue);
    }

    @Override
    public BigInteger getBigInteger(String key)
    {
        return this.getCurrentConfig().getBigInteger(key);
    }

    @Override
    public boolean getBoolean(String key, boolean defaultValue)
    {
        return this.getCurrentConfig().getBoolean(key, defaultValue);
    }

    @Override
    public Boolean getBoolean(String key, Boolean defaultValue)
    {
        return this.getCurrentConfig().getBoolean(key, defaultValue);
    }

    @Override
    public boolean getBoolean(String key)
    {
        return this.getCurrentConfig().getBoolean(key);
    }

    @Override
    public byte getByte(String key, byte defaultValue)
    {
        return this.getCurrentConfig().getByte(key, defaultValue);
    }

    @Override
    public Byte getByte(String key, Byte defaultValue)
    {
        return this.getCurrentConfig().getByte(key, defaultValue);
    }

    @Override
    public byte getByte(String key)
    {
        return this.getCurrentConfig().getByte(key);
    }

    @Override
    public double getDouble(String key, double defaultValue)
    {
        return this.getCurrentConfig().getDouble(key, defaultValue);
    }

    @Override
    public Double getDouble(String key, Double defaultValue)
    {
        return this.getCurrentConfig().getDouble(key, defaultValue);
    }

    @Override
    public double getDouble(String key)
    {
        return this.getCurrentConfig().getDouble(key);
    }

    @Override
    public float getFloat(String key, float defaultValue)
    {
        return this.getCurrentConfig().getFloat(key, defaultValue);
    }

    @Override
    public Float getFloat(String key, Float defaultValue)
    {
        return this.getCurrentConfig().getFloat(key, defaultValue);
    }

    @Override
    public float getFloat(String key)
    {
        return this.getCurrentConfig().getFloat(key);
    }

    @Override
    public int getInt(String key, int defaultValue)
    {
        return this.getCurrentConfig().getInt(key, defaultValue);
    }

    @Override
    public int getInt(String key)
    {
        return this.getCurrentConfig().getInt(key);
    }

    @Override
    public Integer getInteger(String key, Integer defaultValue)
    {
        return this.getCurrentConfig().getInteger(key, defaultValue);
    }

    @Override
    protected Iterator<String> getKeysInternal()
    {
        return this.getCurrentConfig().getKeys();
    }

    @Override
    protected Iterator<String> getKeysInternal(String prefix)
    {
        return this.getCurrentConfig().getKeys(prefix);
    }

    @Override
    public List<Object> getList(String key, List<Object> defaultValue)
    {
        return this.getCurrentConfig().getList(key, defaultValue);
    }

    @Override
    public List<Object> getList(String key)
    {
        return this.getCurrentConfig().getList(key);
    }

    @Override
    public long getLong(String key, long defaultValue)
    {
        return this.getCurrentConfig().getLong(key, defaultValue);
    }

    @Override
    public Long getLong(String key, Long defaultValue)
    {
        return this.getCurrentConfig().getLong(key, defaultValue);
    }

    @Override
    public long getLong(String key)
    {
        return this.getCurrentConfig().getLong(key);
    }

    @Override
    public Properties getProperties(String key)
    {
        return this.getCurrentConfig().getProperties(key);
    }

    @Override
    protected Object getPropertyInternal(String key)
    {
        return this.getCurrentConfig().getProperty(key);
    }

    @Override
    public short getShort(String key, short defaultValue)
    {
        return this.getCurrentConfig().getShort(key, defaultValue);
    }

    @Override
    public Short getShort(String key, Short defaultValue)
    {
        return this.getCurrentConfig().getShort(key, defaultValue);
    }

    @Override
    public short getShort(String key)
    {
        return this.getCurrentConfig().getShort(key);
    }

    @Override
    public String getString(String key, String defaultValue)
    {
        return this.getCurrentConfig().getString(key, defaultValue);
    }

    @Override
    public String getString(String key)
    {
        return this.getCurrentConfig().getString(key);
    }

    @Override
    public String[] getStringArray(String key)
    {
        return this.getCurrentConfig().getStringArray(key);
    }

    @Override
    protected boolean isEmptyInternal()
    {
        return this.getCurrentConfig().isEmpty();
    }

    @Override
    protected int sizeInternal() {
        return this.getCurrentConfig().size();
    }

    @Override
    protected void setPropertyInternal(String key, Object value)
    {
        if (configs != null)
        {
            this.getCurrentConfig().setProperty(key, value);
        }
    }

    @Override
    public Configuration subset(String prefix)
    {
        return this.getCurrentConfig().subset(prefix);
    }

    @Override
    public ExpressionEngine getExpressionEngine()
    {
        return super.getExpressionEngine();
    }

    @Override
    public void setExpressionEngine(ExpressionEngine expressionEngine)
    {
        super.setExpressionEngine(expressionEngine);
    }

    @Override
    protected void addNodesInternal(String key, Collection<? extends ImmutableNode> nodes)
    {
        this.getCurrentConfig().addNodes(key, nodes);
    }

    @Override
    public HierarchicalConfiguration<ImmutableNode> configurationAt(String key, boolean supportUpdates)
    {
        return this.getCurrentConfig().configurationAt(key, supportUpdates);
    }

    @Override
    public HierarchicalConfiguration<ImmutableNode> configurationAt(String key)
    {
        return this.getCurrentConfig().configurationAt(key);
    }

    @Override
    public List<HierarchicalConfiguration<ImmutableNode>> configurationsAt(String key)
    {
        return this.getCurrentConfig().configurationsAt(key);
    }

    @Override
    protected Object clearTreeInternal(String key)
    {
        this.getCurrentConfig().clearTree(key);
        return Collections.emptyList();
    }

    @Override
    protected int getMaxIndexInternal(String key)
    {
        return this.getCurrentConfig().getMaxIndex(key);
    }

    @Override
    public Configuration interpolatedConfiguration()
    {
        return this.getCurrentConfig().interpolatedConfiguration();
    }


    /**
     * Returns the configuration source, in which the specified key is defined.
     * This method will determine the configuration node that is identified by
     * the given key. The following constellations are possible:
     * <ul>
     * <li>If no node object is found for this key, <b>null</b> is returned.</li>
     * <li>If the key maps to multiple nodes belonging to different
     * configuration sources, a {@code IllegalArgumentException} is
     * thrown (in this case no unique source can be determined).</li>
     * <li>If exactly one node is found for the key, the (child) configuration
     * object, to which the node belongs is determined and returned.</li>
     * <li>For keys that have been added directly to this combined
     * configuration and that do not belong to the namespaces defined by
     * existing child configurations this configuration will be returned.</li>
     * </ul>
     *
     * @param key the key of a configuration property
     * @return the configuration, to which this property belongs or <b>null</b>
     * if the key cannot be resolved
     * @throws IllegalArgumentException if the key maps to multiple properties
     * and the source cannot be determined, or if the key is <b>null</b>
     */
    @Override
    public Configuration getSource(String key)
    {
        if (key == null)
        {
            throw new IllegalArgumentException("Key must not be null!");
        }
        return getCurrentConfig().getSource(key);
    }

    @Override
    public void clearEventListeners()
    {
        for (CombinedConfiguration cc : configs.values())
        {
            cc.clearEventListeners();
        }
        super.clearEventListeners();
    }

    @Override
    public <T extends Event> void addEventListener(EventType<T> eventType,
            EventListener<? super T> listener)
    {
        for (CombinedConfiguration cc : configs.values())
        {
            cc.addEventListener(eventType, listener);
        }
        super.addEventListener(eventType, listener);
    }

    @Override
    public <T extends Event> boolean removeEventListener(
            EventType<T> eventType, EventListener<? super T> listener)
    {
        for (CombinedConfiguration cc : configs.values())
        {
            cc.removeEventListener(eventType, listener);
        }
        return super.removeEventListener(eventType, listener);
    }

    @Override
    public void clearErrorListeners()
    {
        for (CombinedConfiguration cc : configs.values())
        {
            cc.clearErrorListeners();
        }
        super.clearErrorListeners();
    }

    /**
     * Returns a copy of this object. This implementation performs a deep clone,
     * i.e. all contained configurations will be cloned, too. For this to work,
     * all contained configurations must be cloneable. Registered event
     * listeners won't be cloned. The clone will use the same node combiner than
     * the original.
     *
     * @return the copied object
     */
    @Override
    public Object clone()
    {
        return super.clone();
    }

    /**
     * Invalidates the current combined configuration. This means that the next time a
     * property is accessed the combined node structure must be re-constructed.
     * Invalidation of a combined configuration also means that an event of type
     * {@code EVENT_COMBINED_INVALIDATE} is fired. Note that while other
     * events most times appear twice (once before and once after an update),
     * this event is only fired once (after update).
     */
    @Override
    public void invalidate()
    {
        getCurrentConfig().invalidate();
    }

    public void invalidateAll()
    {
        for (CombinedConfiguration cc : configs.values())
        {
            cc.invalidate();
        }
    }

    /**
     * {@inheritDoc} This implementation ensures that the current configuration
     * is initialized. The lock counter is increased.
     */
    @Override
    protected void beginRead(boolean optimize)
    {
        CurrentConfigHolder cch = ensureCurrentConfiguration();
        cch.incrementLockCount();
        if (!optimize && cch.getCurrentConfiguration() == null)
        {
            // delegate to beginWrite() which creates the child configuration
            beginWrite(optimize);
            endWrite();
        }

        // This actually uses our own synchronizer
        cch.getCurrentConfiguration().beginRead(optimize);
    }

    /**
     * {@inheritDoc} This implementation ensures that the current configuration
     * is initialized. If necessary, a new child configuration instance is
     * created.
     */
    @Override
    protected void beginWrite(boolean optimize)
    {
        CurrentConfigHolder cch = ensureCurrentConfiguration();
        cch.incrementLockCount();

        super.beginWrite(optimize);
        if (!optimize && cch.getCurrentConfiguration() == null)
        {
            cch.setCurrentConfiguration(createChildConfiguration());
            configs.put(cch.getKey(), cch.getCurrentConfiguration());
            initChildConfiguration(cch.getCurrentConfiguration());
        }
    }

    /**
     * {@inheritDoc} This implementation clears the current configuration if
     * necessary.
     */
    @Override
    protected void endRead()
    {
        currentConfig.get().getCurrentConfiguration().endRead();
        releaseLock();
    }

    /**
     * {@inheritDoc} This implementation clears the current configuration if
     * necessary.
     */
    @Override
    protected void endWrite()
    {
        super.endWrite();
        releaseLock();
    }

    /**
     * Decrements the lock count of the current configuration holder. If it
     * reaches 0, the current configuration is removed. (It is then reevaluated
     * when the next operation starts.)
     */
    private void releaseLock()
    {
        CurrentConfigHolder cch = currentConfig.get();
        assert cch != null : "No current configuration!";
        if(cch.decrementLockCountAndCheckRelease())
        {
            currentConfig.remove();
        }
    }

    /**
     * Returns the current configuration. This configuration was initialized at
     * the beginning of an operation and stored in a thread-local variable. Some
     * methods of this class call this method directly without requesting a lock
     * before. To deal with this, we always request an additional read lock.
     *
     * @return the current configuration
     */
    private CombinedConfiguration getCurrentConfig()
    {
        CombinedConfiguration config;
        String key;
        beginRead(false);
        try
        {
            config = currentConfig.get().getCurrentConfiguration();
            key = currentConfig.get().getKey();
        }
        finally
        {
            endRead();
        }

        if (getLogger().isDebugEnabled())
        {
            getLogger().debug("Returning config for " + key + ": " + config);
        }
        return config;
    }

    /**
     * Creates a new, uninitialized child configuration.
     *
     * @return the new child configuration
     */
    private CombinedConfiguration createChildConfiguration()
    {
        return new CombinedConfiguration(getNodeCombiner());
    }

    /**
     * Initializes a newly created child configuration. This method copies a
     * bunch of settings from this instance to the child configuration.
     *
     * @param config the child configuration to be initialized
     */
    private void initChildConfiguration(CombinedConfiguration config)
    {
        if (loggerName != null)
        {
            Log log = LogFactory.getLog(loggerName);
            if (log != null)
            {
                config.setLogger(log);
            }
        }
        config.setExpressionEngine(this.getExpressionEngine());
        config.setConversionExpressionEngine(getConversionExpressionEngine());
        config.setListDelimiterHandler(getListDelimiterHandler());
        copyEventListeners(config);
        for (ConfigData data : configurations)
        {
            config.addConfiguration(data.getConfiguration(), data.getName(),
                    data.getAt());
        }
        config.setSynchronizer(getSynchronizer());
    }

    /**
     * Creates a {@code ConfigurationInterpolator} instance for performing local
     * variable substitutions. This implementation returns an object which
     * shares the prefix lookups from this configuration's
     * {@code ConfigurationInterpolator}, but does not define any other lookups.
     *
     * @return the {@code ConfigurationInterpolator}
     */
    private ConfigurationInterpolator initLocalInterpolator()
    {
        ConfigurationInterpolator ci = new ConfigurationInterpolator()
        {
            @Override
            protected Lookup fetchLookupForPrefix(String prefix)
            {
                return ConfigurationInterpolator
                        .nullSafeLookup(getInterpolator().getLookups().get(
                                prefix));
            }
        };
        return ci;
    }

    /**
     * Checks whether the current configuration is set. If not, a
     * {@code CurrentConfigHolder} is now created and initialized, and
     * associated with the current thread. The member for the current
     * configuration is undefined if for the current key no configuration exists
     * yet.
     *
     * @return the {@code CurrentConfigHolder} instance for the current thread
     */
    private CurrentConfigHolder ensureCurrentConfiguration()
    {
        CurrentConfigHolder cch = currentConfig.get();
        if (cch == null)
        {
            String key = String.valueOf(localSubst.interpolate(keyPattern));
            cch = new CurrentConfigHolder(key);
            cch.setCurrentConfiguration(configs.get(key));
            currentConfig.set(cch);
        }
        return cch;
    }

    /**
     * Internal class that identifies each Configuration.
     */
    static class ConfigData
    {
        /** Stores a reference to the configuration. */
        private final Configuration configuration;

        /** Stores the name under which the configuration is stored. */
        private final String name;

        /** Stores the at string.*/
        private final String at;

        /**
         * Creates a new instance of {@code ConfigData} and initializes
         * it.
         *
         * @param config the configuration
         * @param n the name
         * @param at the at position
         */
        public ConfigData(Configuration config, String n, String at)
        {
            configuration = config;
            name = n;
            this.at = at;
        }

        /**
         * Returns the stored configuration.
         *
         * @return the configuration
         */
        public Configuration getConfiguration()
        {
            return configuration;
        }

        /**
         * Returns the configuration's name.
         *
         * @return the name
         */
        public String getName()
        {
            return name;
        }

        /**
         * Returns the at position of this configuration.
         *
         * @return the at position
         */
        public String getAt()
        {
            return at;
        }

    }

    /**
     * A simple data class holding information about the current configuration
     * while an operation for a thread is processed.
     */
    private static class CurrentConfigHolder
    {
        /** Stores the current configuration of the current thread. */
        private CombinedConfiguration currentConfiguration;

        /**
         * Stores the key of the configuration evaluated for the current thread
         * at the beginning of an operation.
         */
        private final String key;

        /** A counter for reentrant locks. */
        private int lockCount;

        /**
         * Creates a new instance of {@code CurrentConfigHolder} and initializes
         * it with the key for the current configuration.
         *
         * @param curKey the current key
         */
        public CurrentConfigHolder(String curKey)
        {
            key = curKey;
        }

        /**
         * Returns the current configuration.
         *
         * @return the current configuration
         */
        public CombinedConfiguration getCurrentConfiguration()
        {
            return currentConfiguration;
        }

        /**
         * Sets the current configuration.
         *
         * @param currentConfiguration the current configuration
         */
        public void setCurrentConfiguration(
                CombinedConfiguration currentConfiguration)
        {
            this.currentConfiguration = currentConfiguration;
        }

        /**
         * Returns the current key.
         *
         * @return the current key
         */
        public String getKey()
        {
            return key;
        }

        /**
         * Increments the lock counter.
         */
        public void incrementLockCount()
        {
            lockCount++;
        }

        /**
         * Decrements the lock counter and checks whether it has reached 0. In
         * this cause, the operation is complete, and the lock can be released.
         *
         * @return <b>true</b> if the lock count reaches 0, <b>false</b>
         *         otherwise
         */
        public boolean decrementLockCountAndCheckRelease()
        {
            return --lockCount == 0;
        }
    }
}
