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