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