001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *     http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.configuration2;
018
019import java.math.BigDecimal;
020import java.math.BigInteger;
021import java.util.ArrayList;
022import java.util.Collection;
023import java.util.Collections;
024import java.util.HashMap;
025import java.util.Iterator;
026import java.util.List;
027import java.util.Map;
028import java.util.Properties;
029import java.util.Set;
030import java.util.concurrent.ConcurrentHashMap;
031import java.util.concurrent.ConcurrentMap;
032
033import org.apache.commons.configuration2.event.BaseEventSource;
034import org.apache.commons.configuration2.event.Event;
035import org.apache.commons.configuration2.event.EventListener;
036import org.apache.commons.configuration2.event.EventType;
037import org.apache.commons.configuration2.interpol.ConfigurationInterpolator;
038import org.apache.commons.configuration2.interpol.Lookup;
039import org.apache.commons.configuration2.io.ConfigurationLogger;
040import org.apache.commons.configuration2.tree.ImmutableNode;
041import org.apache.commons.configuration2.tree.NodeCombiner;
042
043/**
044 * <p>
045 * DynamicCombinedConfiguration allows a set of CombinedConfigurations to be used.
046 * </p>
047 * <p>
048 * Each CombinedConfiguration is referenced by a key that is dynamically constructed from a key pattern on each call.
049 * The key pattern will be resolved using the configured ConfigurationInterpolator.
050 * </p>
051 * <p>
052 * This Configuration implementation uses the configured {@code Synchronizer} to guard itself against concurrent access.
053 * If there are multiple threads accessing an instance concurrently, a fully functional {@code Synchronizer}
054 * implementation (e.g. {@code ReadWriteSynchronizer}) has to be used to ensure consistency and to avoid exceptions. The
055 * {@code Synchronizer} assigned to an instance is also passed to child configuration objects when they are created.
056 * </p>
057 *
058 * @since 1.6
059 */
060public class DynamicCombinedConfiguration extends CombinedConfiguration {
061    /**
062     * 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 <b>true</b> if the lock count reaches 0, <b>false</b> 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 <b>null</b>, 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 <b>null</b>)
242     * @param name the name of this configuration (can be <b>null</b>)
243     * @param at the position of this configuration in the combined tree (can be <b>null</b>)
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 <b>null</b> 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 <b>null</b>)
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    public String getKeyPattern() {
586        return this.keyPattern;
587    }
588
589    @Override
590    protected Iterator<String> getKeysInternal() {
591        return getCurrentConfig().getKeys();
592    }
593
594    @Override
595    protected Iterator<String> getKeysInternal(final String prefix) {
596        return getCurrentConfig().getKeys(prefix);
597    }
598
599    @Override
600    public List<Object> getList(final String key) {
601        return getCurrentConfig().getList(key);
602    }
603
604    @Override
605    public List<Object> getList(final String key, final List<?> defaultValue) {
606        return getCurrentConfig().getList(key, defaultValue);
607    }
608
609    @Override
610    public long getLong(final String key) {
611        return getCurrentConfig().getLong(key);
612    }
613
614    @Override
615    public long getLong(final String key, final long defaultValue) {
616        return getCurrentConfig().getLong(key, defaultValue);
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    protected int getMaxIndexInternal(final String key) {
626        return getCurrentConfig().getMaxIndex(key);
627    }
628
629    /**
630     * Gets the node combiner that is used for creating the combined node structure.
631     *
632     * @return the node combiner
633     */
634    @Override
635    public NodeCombiner getNodeCombiner() {
636        return nodeCombiner;
637    }
638
639    /**
640     * Gets the number of configurations that are contained in this combined configuration.
641     *
642     * @return the number of contained configurations
643     */
644    @Override
645    public int getNumberOfConfigurations() {
646        beginRead(false);
647        try {
648            return configurations.size();
649        } finally {
650            endRead();
651        }
652    }
653
654    @Override
655    public Properties getProperties(final String key) {
656        return getCurrentConfig().getProperties(key);
657    }
658
659    @Override
660    protected Object getPropertyInternal(final String key) {
661        return getCurrentConfig().getProperty(key);
662    }
663
664    @Override
665    public short getShort(final String key) {
666        return getCurrentConfig().getShort(key);
667    }
668
669    @Override
670    public short getShort(final String key, final short defaultValue) {
671        return getCurrentConfig().getShort(key, defaultValue);
672    }
673
674    @Override
675    public Short getShort(final String key, final Short defaultValue) {
676        return getCurrentConfig().getShort(key, defaultValue);
677    }
678
679    /**
680     * Gets the configuration source, in which the specified key is defined. This method will determine the configuration
681     * node that is identified by the given key. The following constellations are possible:
682     * <ul>
683     * <li>If no node object is found for this key, <b>null</b> is returned.</li>
684     * <li>If the key maps to multiple nodes belonging to different configuration sources, a
685     * {@code IllegalArgumentException} is thrown (in this case no unique source can be determined).</li>
686     * <li>If exactly one node is found for the key, the (child) configuration object, to which the node belongs is
687     * determined and returned.</li>
688     * <li>For keys that have been added directly to this combined configuration and that do not belong to the namespaces
689     * defined by existing child configurations this configuration will be returned.</li>
690     * </ul>
691     *
692     * @param key the key of a configuration property
693     * @return the configuration, to which this property belongs or <b>null</b> if the key cannot be resolved
694     * @throws IllegalArgumentException if the key maps to multiple properties and the source cannot be determined, or if
695     *         the key is <b>null</b>
696     */
697    @Override
698    public Configuration getSource(final String key) {
699        if (key == null) {
700            throw new IllegalArgumentException("Key must not be null!");
701        }
702        return getCurrentConfig().getSource(key);
703    }
704
705    @Override
706    public String getString(final String key) {
707        return getCurrentConfig().getString(key);
708    }
709
710    @Override
711    public String getString(final String key, final String defaultValue) {
712        return getCurrentConfig().getString(key, defaultValue);
713    }
714
715    @Override
716    public String[] getStringArray(final String key) {
717        return getCurrentConfig().getStringArray(key);
718    }
719
720    /**
721     * Initializes a newly created child configuration. This method copies a bunch of settings from this instance to the
722     * child configuration.
723     *
724     * @param config the child configuration to be initialized
725     */
726    private void initChildConfiguration(final CombinedConfiguration config) {
727        if (loggerName != null) {
728            config.setLogger(new ConfigurationLogger(loggerName));
729        }
730        config.setExpressionEngine(getExpressionEngine());
731        config.setConversionExpressionEngine(getConversionExpressionEngine());
732        config.setListDelimiterHandler(getListDelimiterHandler());
733        copyEventListeners(config);
734        configurations.forEach(data -> config.addConfiguration(data.getConfiguration(), data.getName(), data.getAt()));
735        config.setSynchronizer(getSynchronizer());
736    }
737
738    /**
739     * Creates a {@code ConfigurationInterpolator} instance for performing local variable substitutions. This implementation
740     * returns an object which shares the prefix lookups from this configuration's {@code ConfigurationInterpolator}, but
741     * does not define any other lookups.
742     *
743     * @return the {@code ConfigurationInterpolator}
744     */
745    private ConfigurationInterpolator initLocalInterpolator() {
746        return new ConfigurationInterpolator() {
747            @Override
748            protected Lookup fetchLookupForPrefix(final String prefix) {
749                return nullSafeLookup(getInterpolator().getLookups().get(prefix));
750            }
751        };
752    }
753
754    @Override
755    public Configuration interpolatedConfiguration() {
756        return getCurrentConfig().interpolatedConfiguration();
757    }
758
759    /**
760     * Invalidates the current combined configuration. This means that the next time a property is accessed the combined
761     * node structure must be re-constructed. Invalidation of a combined configuration also means that an event of type
762     * {@code EVENT_COMBINED_INVALIDATE} is fired. Note that while other events most times appear twice (once before and
763     * once after an update), this event is only fired once (after update).
764     */
765    @Override
766    public void invalidate() {
767        getCurrentConfig().invalidate();
768    }
769
770    public void invalidateAll() {
771        configs.values().forEach(CombinedConfiguration::invalidate);
772    }
773
774    @Override
775    protected boolean isEmptyInternal() {
776        return getCurrentConfig().isEmpty();
777    }
778
779    /**
780     * Decrements the lock count of the current configuration holder. If it reaches 0, the current configuration is removed.
781     * (It is then reevaluated when the next operation starts.)
782     */
783    private void releaseLock() {
784        final CurrentConfigHolder cch = CURRENT_CONFIG.get();
785        assert cch != null : "No current configuration!";
786        if (cch.decrementLockCountAndCheckRelease()) {
787            CURRENT_CONFIG.remove();
788        }
789    }
790
791    /**
792     * Removes the specified configuration from this combined configuration.
793     *
794     * @param config the configuration to be removed
795     * @return a flag whether this configuration was found and could be removed
796     */
797    @Override
798    public boolean removeConfiguration(final Configuration config) {
799        beginWrite(false);
800        try {
801            for (int index = 0; index < getNumberOfConfigurations(); index++) {
802                if (configurations.get(index).getConfiguration() == config) {
803                    removeConfigurationAt(index);
804                    return true;
805                }
806            }
807
808            return false;
809        } finally {
810            endWrite();
811        }
812    }
813
814    /**
815     * Removes the configuration with the specified name.
816     *
817     * @param name the name of the configuration to be removed
818     * @return the removed configuration (<b>null</b> if this configuration was not found)
819     */
820    @Override
821    public Configuration removeConfiguration(final String name) {
822        final Configuration conf = getConfiguration(name);
823        if (conf != null) {
824            removeConfiguration(conf);
825        }
826        return conf;
827    }
828
829    /**
830     * Removes the configuration at the specified index.
831     *
832     * @param index the index
833     * @return the removed configuration
834     */
835    @Override
836    public Configuration removeConfigurationAt(final int index) {
837        beginWrite(false);
838        try {
839            final ConfigData cd = configurations.remove(index);
840            if (cd.getName() != null) {
841                namedConfigurations.remove(cd.getName());
842            }
843            return cd.getConfiguration();
844        } finally {
845            endWrite();
846        }
847    }
848
849    @Override
850    public <T extends Event> boolean removeEventListener(final EventType<T> eventType, final EventListener<? super T> listener) {
851        configs.values().forEach(cc -> cc.removeEventListener(eventType, listener));
852        return super.removeEventListener(eventType, listener);
853    }
854
855    public void setKeyPattern(final String pattern) {
856        this.keyPattern = pattern;
857    }
858
859    /**
860     * Sets the name of the Logger to use on each CombinedConfiguration.
861     *
862     * @param name The Logger name.
863     */
864    public void setLoggerName(final String name) {
865        this.loggerName = name;
866    }
867
868    /**
869     * Sets the node combiner. This object will be used when the combined node structure is to be constructed. It must not
870     * be <b>null</b>, otherwise an {@code IllegalArgumentException} exception is thrown. Changing the node combiner causes
871     * an invalidation of this combined configuration, so that the new combiner immediately takes effect.
872     *
873     * @param nodeCombiner the node combiner
874     */
875    @Override
876    public void setNodeCombiner(final NodeCombiner nodeCombiner) {
877        if (nodeCombiner == null) {
878            throw new IllegalArgumentException("Node combiner must not be null!");
879        }
880        this.nodeCombiner = nodeCombiner;
881        invalidateAll();
882    }
883
884    @Override
885    protected void setPropertyInternal(final String key, final Object value) {
886        getCurrentConfig().setProperty(key, value);
887    }
888
889    @Override
890    protected int sizeInternal() {
891        return getCurrentConfig().size();
892    }
893
894    @Override
895    public Configuration subset(final String prefix) {
896        return getCurrentConfig().subset(prefix);
897    }
898}