View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.configuration2;
18  
19  import java.math.BigDecimal;
20  import java.math.BigInteger;
21  import java.util.ArrayList;
22  import java.util.Collection;
23  import java.util.Collections;
24  import java.util.HashMap;
25  import java.util.Iterator;
26  import java.util.List;
27  import java.util.Map;
28  import java.util.Properties;
29  import java.util.Set;
30  import java.util.concurrent.ConcurrentHashMap;
31  import java.util.concurrent.ConcurrentMap;
32  
33  import org.apache.commons.configuration2.event.BaseEventSource;
34  import org.apache.commons.configuration2.event.Event;
35  import org.apache.commons.configuration2.event.EventListener;
36  import org.apache.commons.configuration2.event.EventType;
37  import org.apache.commons.configuration2.interpol.ConfigurationInterpolator;
38  import org.apache.commons.configuration2.interpol.Lookup;
39  import org.apache.commons.configuration2.io.ConfigurationLogger;
40  import org.apache.commons.configuration2.tree.ImmutableNode;
41  import org.apache.commons.configuration2.tree.NodeCombiner;
42  
43  /**
44   * <p>
45   * DynamicCombinedConfiguration allows a set of CombinedConfigurations to be used.
46   * </p>
47   * <p>
48   * Each CombinedConfiguration is referenced by a key that is dynamically constructed from a key pattern on each call.
49   * The key pattern will be resolved using the configured ConfigurationInterpolator.
50   * </p>
51   * <p>
52   * This Configuration implementation uses the configured {@code Synchronizer} to guard itself against concurrent access.
53   * If there are multiple threads accessing an instance concurrently, a fully functional {@code Synchronizer}
54   * implementation (e.g. {@code ReadWriteSynchronizer}) has to be used to ensure consistency and to avoid exceptions. The
55   * {@code Synchronizer} assigned to an instance is also passed to child configuration objects when they are created.
56   * </p>
57   *
58   * @since 1.6
59   */
60  public class DynamicCombinedConfiguration extends CombinedConfiguration {
61      /**
62       * Stores the current configuration for each involved thread. This value is set at the beginning of an operation and
63       * removed at the end.
64       */
65      private static final ThreadLocal<CurrentConfigHolder> CURRENT_CONFIG = new ThreadLocal<>();
66  
67      /** The CombinedConfigurations */
68      private final ConcurrentMap<String, CombinedConfiguration> configs = new ConcurrentHashMap<>();
69  
70      /** Stores a list with the contained configurations. */
71      private final List<ConfigData> configurations = new ArrayList<>();
72  
73      /** Stores a map with the named configurations. */
74      private final Map<String, Configuration> namedConfigurations = new HashMap<>();
75  
76      /** The key pattern for the CombinedConfiguration map */
77      private String keyPattern;
78  
79      /** Stores the combiner. */
80      private NodeCombiner nodeCombiner;
81  
82      /** The name of the logger to use for each CombinedConfiguration */
83      private String loggerName = DynamicCombinedConfiguration.class.getName();
84  
85      /** The object for handling variable substitution in key patterns. */
86      private final ConfigurationInterpolator localSubst;
87  
88      /**
89       * Creates a new instance of {@code DynamicCombinedConfiguration} and initializes the combiner to be used.
90       *
91       * @param comb the node combiner (can be <b>null</b>, then a union combiner is used as default)
92       */
93      public DynamicCombinedConfiguration(final NodeCombiner comb) {
94          setNodeCombiner(comb);
95          initLogger(new ConfigurationLogger(DynamicCombinedConfiguration.class));
96          localSubst = initLocalInterpolator();
97      }
98  
99      /**
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 }