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