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