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     * Sets 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 final 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}