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 *     https://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    /**
063     * Internal class that identifies each Configuration.
064     */
065    static class ConfigData {
066
067        /** Stores a reference to the configuration. */
068        private final Configuration configuration;
069
070        /** Stores the name under which the configuration is stored. */
071        private final String name;
072
073        /** Stores the at string. */
074        private final String at;
075
076        /**
077         * Creates a new instance of {@code ConfigData} and initializes it.
078         *
079         * @param config the configuration
080         * @param n the name
081         * @param at the at position
082         */
083        public ConfigData(final Configuration config, final String n, final String at) {
084            configuration = config;
085            name = n;
086            this.at = at;
087        }
088
089        /**
090         * Gets the at position of this configuration.
091         *
092         * @return the at position
093         */
094        public String getAt() {
095            return at;
096        }
097
098        /**
099         * 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}