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.io.ByteArrayOutputStream;
020import java.io.PrintStream;
021import java.util.ArrayList;
022import java.util.Collection;
023import java.util.HashMap;
024import java.util.HashSet;
025import java.util.Iterator;
026import java.util.List;
027import java.util.Map;
028import java.util.Set;
029import java.util.stream.Collectors;
030
031import org.apache.commons.configuration2.event.ConfigurationEvent;
032import org.apache.commons.configuration2.event.EventListener;
033import org.apache.commons.configuration2.event.EventSource;
034import org.apache.commons.configuration2.event.EventType;
035import org.apache.commons.configuration2.ex.ConfigurationRuntimeException;
036import org.apache.commons.configuration2.sync.LockMode;
037import org.apache.commons.configuration2.tree.DefaultConfigurationKey;
038import org.apache.commons.configuration2.tree.DefaultExpressionEngine;
039import org.apache.commons.configuration2.tree.ExpressionEngine;
040import org.apache.commons.configuration2.tree.ImmutableNode;
041import org.apache.commons.configuration2.tree.NodeCombiner;
042import org.apache.commons.configuration2.tree.NodeTreeWalker;
043import org.apache.commons.configuration2.tree.QueryResult;
044import org.apache.commons.configuration2.tree.TreeUtils;
045import org.apache.commons.configuration2.tree.UnionCombiner;
046import org.apache.commons.lang3.StringUtils;
047
048/**
049 * <p>
050 * A hierarchical composite configuration class.
051 * </p>
052 * <p>
053 * This class maintains a list of configuration objects, which can be added using the diverse {@code addConfiguration()}
054 * methods. After that the configurations can be accessed either by name (if one was provided when the configuration was
055 * added) or by index. For the whole set of managed configurations a logical node structure is constructed. For this
056 * purpose a {@link org.apache.commons.configuration2.tree.NodeCombiner NodeCombiner} object can be set. This makes it
057 * possible to specify different algorithms for the combination process.
058 * </p>
059 * <p>
060 * The big advantage of this class is that it creates a truly hierarchical structure of all the properties stored in the
061 * contained configurations - even if some of them are no hierarchical configurations per se. So all enhanced features
062 * provided by a hierarchical configuration (e.g. choosing an expression engine) are applicable.
063 * </p>
064 * <p>
065 * The class works by registering itself as an event listener at all added configurations. So it gets notified whenever
066 * one of these configurations is changed and can invalidate its internal node structure. The next time a property is
067 * accessed the node structure will be re-constructed using the current state of the managed configurations. Note that,
068 * depending on the used {@code NodeCombiner}, this may be a complex operation.
069 * </p>
070 * <p>
071 * Because of the way a {@code CombinedConfiguration} is working it has more or less view character: it provides a logic
072 * view on the configurations it contains. In this constellation not all methods defined for hierarchical configurations
073 * - especially methods that update the stored properties - can be implemented in a consistent manner. Using such
074 * methods (like {@code addProperty()}, or {@code clearProperty()} on a {@code CombinedConfiguration} is not strictly
075 * forbidden, however, depending on the current {@link NodeCombiner} and the involved properties, the results may be
076 * different than expected. Some examples may illustrate this:
077 * </p>
078 * <ul>
079 * <li>Imagine a {@code CombinedConfiguration} <em>cc</em> containing two child configurations with the following
080 * content:
081 * <dl>
082 * <dt>user.properties</dt>
083 * <dd>
084 *
085 * <pre>
086 * gui.background = blue
087 * gui.position = (10, 10, 400, 200)
088 * </pre>
089 *
090 * </dd>
091 * <dt>default.properties</dt>
092 * <dd>
093 *
094 * <pre>
095 * gui.background = black
096 * gui.foreground = white
097 * home.dir = /data
098 * </pre>
099 *
100 * </dd>
101 * </dl>
102 * As a {@code NodeCombiner} a {@link org.apache.commons.configuration2.tree.OverrideCombiner OverrideCombiner} is used.
103 * This combiner will ensure that defined user settings take precedence over the default values. If the resulting
104 * {@code CombinedConfiguration} is queried for the background color, {@code blue} will be returned because this value
105 * is defined in {@code user.properties}. Now consider what happens if the key {@code gui.background} is removed from
106 * the {@code CombinedConfiguration}:
107 *
108 * <pre>
109 * cc.clearProperty(&quot;gui.background&quot;);
110 * </pre>
111 *
112 * Will a {@code cc.containsKey("gui.background")} now return <b>false</b>? No, it won't! The {@code clearProperty()}
113 * operation is executed on the node set of the combined configuration, which was constructed from the nodes of the two
114 * child configurations. It causes the value of the <em>background</em> node to be cleared, which is also part of the
115 * first child configuration. This modification of one of its child configurations causes the
116 * {@code CombinedConfiguration} to be re-constructed. This time the {@code OverrideCombiner} cannot find a
117 * {@code gui.background} property in the first child configuration, but it finds one in the second, and adds it to the
118 * resulting combined configuration. So the property is still present (with a different value now).</li>
119 * <li>{@code addProperty()} can also be problematic: Most node combiners use special view nodes for linking parts of
120 * the original configurations' data together. If new properties are added to such a special node, they do not belong to
121 * any of the managed configurations and thus hang in the air. Using the same configurations as in the last example, the
122 * statement
123 *
124 * <pre>
125 * addProperty(&quot;database.user&quot;, &quot;scott&quot;);
126 * </pre>
127 *
128 * would cause such a hanging property. If now one of the child configurations is changed and the
129 * {@code CombinedConfiguration} is re-constructed, this property will disappear! (Add operations are not problematic if
130 * they result in a child configuration being updated. For instance an {@code addProperty("home.url", "localhost");}
131 * will alter the second child configuration - because the prefix <em>home</em> is here already present; when the
132 * {@code CombinedConfiguration} is re-constructed, this change is taken into account.)</li>
133 * </ul>
134 * <p>
135 * Because of such problems it is recommended to perform updates only on the managed child configurations.
136 * </p>
137 * <p>
138 * Whenever the node structure of a {@code CombinedConfiguration} becomes invalid (either because one of the contained
139 * configurations was modified or because the {@code invalidate()} method was directly called) an event is generated. So
140 * this can be detected by interested event listeners. This also makes it possible to add a combined configuration into
141 * another one.
142 * </p>
143 * <p>
144 * Notes about thread-safety: This configuration implementation uses a {@code Synchronizer} object to protect instances
145 * against concurrent access. The concrete {@code Synchronizer} implementation used determines whether an instance of
146 * this class is thread-safe or not. In contrast to other implementations derived from
147 * {@link BaseHierarchicalConfiguration}, thread-safety is an issue here because the nodes structure used by this
148 * configuration has to be constructed dynamically when a child configuration is changed. Therefore, when multiple
149 * threads are involved which also manipulate one of the child configurations, a proper {@code Synchronizer} object
150 * should be set. Note that the {@code Synchronizer} objects used by the child configurations do not really matter.
151 * Because immutable in-memory nodes structures are used for them there is no danger that updates on child
152 * configurations could interfere with read operations on the combined configuration.
153 * </p>
154 *
155 * @since 1.3
156 */
157public class CombinedConfiguration extends BaseHierarchicalConfiguration implements EventListener<ConfigurationEvent> {
158    /**
159     * Constant for the event type fired when the internal node structure of a combined configuration becomes invalid.
160     *
161     * @since 2.0
162     */
163    public static final EventType<ConfigurationEvent> COMBINED_INVALIDATE = new EventType<>(ConfigurationEvent.ANY, "COMBINED_INVALIDATE");
164
165    /** Constant for the expression engine for parsing the at path. */
166    private static final DefaultExpressionEngine AT_ENGINE = DefaultExpressionEngine.INSTANCE;
167
168    /** Constant for the default node combiner. */
169    private static final NodeCombiner DEFAULT_COMBINER = new UnionCombiner();
170
171    /** Constant for a root node for an empty configuration. */
172    private static final ImmutableNode EMPTY_ROOT = new ImmutableNode.Builder().create();
173
174    /** Stores the combiner. */
175    private NodeCombiner nodeCombiner;
176
177    /** Stores a list with the contained configurations. */
178    private List<ConfigData> configurations;
179
180    /** Stores a map with the named configurations. */
181    private Map<String, Configuration> namedConfigurations;
182
183    /**
184     * An expression engine used for converting child configurations to hierarchical ones.
185     */
186    private ExpressionEngine conversionExpressionEngine;
187
188    /** A flag whether this configuration is up-to-date. */
189    private boolean upToDate;
190
191    /**
192     * Creates a new instance of {@code CombinedConfiguration} and initializes the combiner to be used.
193     *
194     * @param comb the node combiner (can be <b>null</b>, then a union combiner is used as default)
195     */
196    public CombinedConfiguration(final NodeCombiner comb) {
197        nodeCombiner = comb != null ? comb : DEFAULT_COMBINER;
198        initChildCollections();
199    }
200
201    /**
202     * Creates a new instance of {@code CombinedConfiguration} that uses a union combiner.
203     *
204     * @see org.apache.commons.configuration2.tree.UnionCombiner
205     */
206    public CombinedConfiguration() {
207        this(null);
208    }
209
210    /**
211     * Gets the node combiner that is used for creating the combined node structure.
212     *
213     * @return the node combiner
214     */
215    public NodeCombiner getNodeCombiner() {
216        beginRead(true);
217        try {
218            return nodeCombiner;
219        } finally {
220            endRead();
221        }
222    }
223
224    /**
225     * Sets the node combiner. This object will be used when the combined node structure is to be constructed. It must not
226     * be <b>null</b>, otherwise an {@code IllegalArgumentException} exception is thrown. Changing the node combiner causes
227     * an invalidation of this combined configuration, so that the new combiner immediately takes effect.
228     *
229     * @param nodeCombiner the node combiner
230     */
231    public void setNodeCombiner(final NodeCombiner nodeCombiner) {
232        if (nodeCombiner == null) {
233            throw new IllegalArgumentException("Node combiner must not be null!");
234        }
235
236        beginWrite(true);
237        try {
238            this.nodeCombiner = nodeCombiner;
239            invalidateInternal();
240        } finally {
241            endWrite();
242        }
243    }
244
245    /**
246     * Gets the {@code ExpressionEngine} for converting flat child configurations to hierarchical ones.
247     *
248     * @return the conversion expression engine
249     * @since 1.6
250     */
251    public ExpressionEngine getConversionExpressionEngine() {
252        beginRead(true);
253        try {
254            return conversionExpressionEngine;
255        } finally {
256            endRead();
257        }
258    }
259
260    /**
261     * Sets the {@code ExpressionEngine} for converting flat child configurations to hierarchical ones. When constructing
262     * the root node for this combined configuration the properties of all child configurations must be combined to a single
263     * hierarchical node structure. In this process, non hierarchical configurations are converted to hierarchical ones
264     * first. This can be problematic if a child configuration contains keys that are no compatible with the default
265     * expression engine used by hierarchical configurations. Therefore it is possible to specify a specific expression
266     * engine to be used for this purpose.
267     *
268     * @param conversionExpressionEngine the conversion expression engine
269     * @see ConfigurationUtils#convertToHierarchical(Configuration, ExpressionEngine)
270     * @since 1.6
271     */
272    public void setConversionExpressionEngine(final ExpressionEngine conversionExpressionEngine) {
273        beginWrite(true);
274        try {
275            this.conversionExpressionEngine = conversionExpressionEngine;
276        } finally {
277            endWrite();
278        }
279    }
280
281    /**
282     * Adds a new configuration to this combined configuration. It is possible (but not mandatory) to give the new
283     * configuration a name. This name must be unique, otherwise a {@code ConfigurationRuntimeException} will be thrown.
284     * With the optional {@code at} argument you can specify where in the resulting node structure the content of the added
285     * configuration should appear. This is a string that uses dots as property delimiters (independent on the current
286     * expression engine). For instance if you pass in the string {@code "database.tables"}, all properties of the added
287     * configuration will occur in this branch.
288     *
289     * @param config the configuration to add (must not be <b>null</b>)
290     * @param name the name of this configuration (can be <b>null</b>)
291     * @param at the position of this configuration in the combined tree (can be <b>null</b>)
292     */
293    public void addConfiguration(final Configuration config, final String name, final String at) {
294        if (config == null) {
295            throw new IllegalArgumentException("Added configuration must not be null!");
296        }
297
298        beginWrite(true);
299        try {
300            if (name != null && namedConfigurations.containsKey(name)) {
301                throw new ConfigurationRuntimeException("A configuration with the name '" + name + "' already exists in this combined configuration!");
302            }
303
304            final ConfigData cd = new ConfigData(config, name, at);
305            if (getLogger().isDebugEnabled()) {
306                getLogger().debug("Adding configuration " + config + " with name " + name);
307            }
308            configurations.add(cd);
309            if (name != null) {
310                namedConfigurations.put(name, config);
311            }
312
313            invalidateInternal();
314        } finally {
315            endWrite();
316        }
317        registerListenerAt(config);
318    }
319
320    /**
321     * Adds a new configuration to this combined configuration with an optional name. The new configuration's properties
322     * will be added under the root of the combined node structure.
323     *
324     * @param config the configuration to add (must not be <b>null</b>)
325     * @param name the name of this configuration (can be <b>null</b>)
326     */
327    public void addConfiguration(final Configuration config, final String name) {
328        addConfiguration(config, name, null);
329    }
330
331    /**
332     * Adds a new configuration to this combined configuration. The new configuration is not given a name. Its properties
333     * will be added under the root of the combined node structure.
334     *
335     * @param config the configuration to add (must not be <b>null</b>)
336     */
337    public void addConfiguration(final Configuration config) {
338        addConfiguration(config, null, null);
339    }
340
341    /**
342     * Gets the number of configurations that are contained in this combined configuration.
343     *
344     * @return the number of contained configurations
345     */
346    public int getNumberOfConfigurations() {
347        beginRead(true);
348        try {
349            return getNumberOfConfigurationsInternal();
350        } finally {
351            endRead();
352        }
353    }
354
355    /**
356     * Gets the configuration at the specified index. The contained configurations are numbered in the order they were
357     * added to this combined configuration. The index of the first configuration is 0.
358     *
359     * @param index the index
360     * @return the configuration at this index
361     */
362    public Configuration getConfiguration(final int index) {
363        beginRead(true);
364        try {
365            final ConfigData cd = configurations.get(index);
366            return cd.getConfiguration();
367        } finally {
368            endRead();
369        }
370    }
371
372    /**
373     * Gets the configuration with the given name. This can be <b>null</b> if no such configuration exists.
374     *
375     * @param name the name of the configuration
376     * @return the configuration with this name
377     */
378    public Configuration getConfiguration(final String name) {
379        beginRead(true);
380        try {
381            return namedConfigurations.get(name);
382        } finally {
383            endRead();
384        }
385    }
386
387    /**
388     * Gets a List of all the configurations that have been added.
389     *
390     * @return A List of all the configurations.
391     * @since 1.7
392     */
393    public List<Configuration> getConfigurations() {
394        beginRead(true);
395        try {
396            return configurations.stream().map(ConfigData::getConfiguration).collect(Collectors.toList());
397        } finally {
398            endRead();
399        }
400    }
401
402    /**
403     * Gets a List of the names of all the configurations that have been added in the order they were added. A NULL value
404     * will be present in the list for each configuration that was added without a name.
405     *
406     * @return A List of all the configuration names.
407     * @since 1.7
408     */
409    public List<String> getConfigurationNameList() {
410        beginRead(true);
411        try {
412            return configurations.stream().map(ConfigData::getName).collect(Collectors.toList());
413        } finally {
414            endRead();
415        }
416    }
417
418    /**
419     * Removes the specified configuration from this combined configuration.
420     *
421     * @param config the configuration to be removed
422     * @return a flag whether this configuration was found and could be removed
423     */
424    public boolean removeConfiguration(final Configuration config) {
425        for (int index = 0; index < getNumberOfConfigurations(); index++) {
426            if (configurations.get(index).getConfiguration() == config) {
427                removeConfigurationAt(index);
428                return true;
429            }
430        }
431
432        return false;
433    }
434
435    /**
436     * Removes the configuration at the specified index.
437     *
438     * @param index the index
439     * @return the removed configuration
440     */
441    public Configuration removeConfigurationAt(final int index) {
442        final ConfigData cd = configurations.remove(index);
443        if (cd.getName() != null) {
444            namedConfigurations.remove(cd.getName());
445        }
446        unregisterListenerAt(cd.getConfiguration());
447        invalidateInternal();
448        return cd.getConfiguration();
449    }
450
451    /**
452     * Removes the configuration with the specified name.
453     *
454     * @param name the name of the configuration to be removed
455     * @return the removed configuration (<b>null</b> if this configuration was not found)
456     */
457    public Configuration removeConfiguration(final String name) {
458        final Configuration conf = getConfiguration(name);
459        if (conf != null) {
460            removeConfiguration(conf);
461        }
462        return conf;
463    }
464
465    /**
466     * Gets a set with the names of all configurations contained in this combined configuration. Of course here are only
467     * these configurations listed, for which a name was specified when they were added.
468     *
469     * @return a set with the names of the contained configurations (never <b>null</b>)
470     */
471    public Set<String> getConfigurationNames() {
472        beginRead(true);
473        try {
474            return namedConfigurations.keySet();
475        } finally {
476            endRead();
477        }
478    }
479
480    /**
481     * Invalidates this combined configuration. This means that the next time a property is accessed the combined node
482     * structure must be re-constructed. Invalidation of a combined configuration also means that an event of type
483     * {@code EVENT_COMBINED_INVALIDATE} is fired. Note that while other events most times appear twice (once before and
484     * once after an update), this event is only fired once (after update).
485     */
486    public void invalidate() {
487        beginWrite(true);
488        try {
489            invalidateInternal();
490        } finally {
491            endWrite();
492        }
493    }
494
495    /**
496     * Event listener call back for configuration update events. This method is called whenever one of the contained
497     * configurations was modified. It invalidates this combined configuration.
498     *
499     * @param event the update event
500     */
501    @Override
502    public void onEvent(final ConfigurationEvent event) {
503        if (event.isBeforeUpdate()) {
504            invalidate();
505        }
506    }
507
508    /**
509     * Clears this configuration. All contained configurations will be removed.
510     */
511    @Override
512    protected void clearInternal() {
513        unregisterListenerAtChildren();
514        initChildCollections();
515        invalidateInternal();
516    }
517
518    /**
519     * Returns a copy of this object. This implementation performs a deep clone, i.e. all contained configurations will be
520     * cloned, too. For this to work, all contained configurations must be cloneable. Registered event listeners won't be
521     * cloned. The clone will use the same node combiner than the original.
522     *
523     * @return the copied object
524     */
525    @Override
526    public Object clone() {
527        beginRead(false);
528        try {
529            final CombinedConfiguration copy = (CombinedConfiguration) super.clone();
530            copy.initChildCollections();
531            configurations.forEach(cd -> copy.addConfiguration(ConfigurationUtils.cloneConfiguration(cd.getConfiguration()), cd.getName(), cd.getAt()));
532
533            return copy;
534        } finally {
535            endRead();
536        }
537    }
538
539    /**
540     * Gets the configuration source, in which the specified key is defined. This method will determine the configuration
541     * node that is identified by the given key. The following constellations are possible:
542     * <ul>
543     * <li>If no node object is found for this key, <b>null</b> is returned.</li>
544     * <li>If the key maps to multiple nodes belonging to different configuration sources, a
545     * {@code IllegalArgumentException} is thrown (in this case no unique source can be determined).</li>
546     * <li>If exactly one node is found for the key, the (child) configuration object, to which the node belongs is
547     * determined and returned.</li>
548     * <li>For keys that have been added directly to this combined configuration and that do not belong to the namespaces
549     * defined by existing child configurations this configuration will be returned.</li>
550     * </ul>
551     *
552     * @param key the key of a configuration property
553     * @return the configuration, to which this property belongs or <b>null</b> if the key cannot be resolved
554     * @throws IllegalArgumentException if the key maps to multiple properties and the source cannot be determined, or if
555     *         the key is <b>null</b>
556     * @since 1.5
557     */
558    public Configuration getSource(final String key) {
559        if (key == null) {
560            throw new IllegalArgumentException("Key must not be null!");
561        }
562
563        final Set<Configuration> sources = getSources(key);
564        if (sources.isEmpty()) {
565            return null;
566        }
567        final Iterator<Configuration> iterator = sources.iterator();
568        final Configuration source = iterator.next();
569        if (iterator.hasNext()) {
570            throw new IllegalArgumentException("The key " + key + " is defined by multiple sources!");
571        }
572        return source;
573    }
574
575    /**
576     * Gets a set with the configuration sources, in which the specified key is defined. This method determines the
577     * configuration nodes that are identified by the given key. It then determines the configuration sources to which these
578     * nodes belong and adds them to the result set. Note the following points:
579     * <ul>
580     * <li>If no node object is found for this key, an empty set is returned.</li>
581     * <li>For keys that have been added directly to this combined configuration and that do not belong to the namespaces
582     * defined by existing child configurations this combined configuration is contained in the result set.</li>
583     * </ul>
584     *
585     * @param key the key of a configuration property
586     * @return a set with the configuration sources, which contain this property
587     * @since 2.0
588     */
589    public Set<Configuration> getSources(final String key) {
590        beginRead(false);
591        try {
592            final List<QueryResult<ImmutableNode>> results = fetchNodeList(key);
593            final Set<Configuration> sources = new HashSet<>();
594
595            results.forEach(result -> {
596                final Set<Configuration> resultSources = findSourceConfigurations(result.getNode());
597                if (resultSources.isEmpty()) {
598                    // key must be defined in combined configuration
599                    sources.add(this);
600                } else {
601                    sources.addAll(resultSources);
602                }
603            });
604
605            return sources;
606        } finally {
607            endRead();
608        }
609    }
610
611    /**
612     * {@inheritDoc} This implementation checks whether a combined root node is available. If not, it is constructed by
613     * requesting a write lock.
614     */
615    @Override
616    protected void beginRead(final boolean optimize) {
617        if (optimize) {
618            // just need a lock, don't construct configuration
619            super.beginRead(true);
620            return;
621        }
622
623        boolean lockObtained = false;
624        do {
625            super.beginRead(false);
626            if (isUpToDate()) {
627                lockObtained = true;
628            } else {
629                // release read lock and try to obtain a write lock
630                endRead();
631                beginWrite(false); // this constructs the root node
632                endWrite();
633            }
634        } while (!lockObtained);
635    }
636
637    /**
638     * {@inheritDoc} This implementation checks whether a combined root node is available. If not, it is constructed now.
639     */
640    @Override
641    protected void beginWrite(final boolean optimize) {
642        super.beginWrite(true);
643        if (optimize) {
644            // just need a lock, don't construct configuration
645            return;
646        }
647
648        boolean success = false;
649        try {
650            if (!isUpToDate()) {
651                getSubConfigurationParentModel().replaceRoot(constructCombinedNode(), this);
652                upToDate = true;
653            }
654            success = true;
655        } finally {
656            if (!success) {
657                endWrite();
658            }
659        }
660    }
661
662    /**
663     * Returns a flag whether this configuration has been invalidated. This means that the combined nodes structure has to
664     * be rebuilt before the configuration can be accessed.
665     *
666     * @return a flag whether this configuration is invalid
667     */
668    private boolean isUpToDate() {
669        return upToDate;
670    }
671
672    /**
673     * Marks this configuration as invalid. This means that the next access re-creates the root node. An invalidate event is
674     * also fired. Note: This implementation expects that an exclusive (write) lock is held on this instance.
675     */
676    private void invalidateInternal() {
677        upToDate = false;
678        fireEvent(COMBINED_INVALIDATE, null, null, false);
679    }
680
681    /**
682     * Initializes internal data structures for storing information about child configurations.
683     */
684    private void initChildCollections() {
685        configurations = new ArrayList<>();
686        namedConfigurations = new HashMap<>();
687    }
688
689    /**
690     * Creates the root node of this combined configuration.
691     *
692     * @return the combined root node
693     */
694    private ImmutableNode constructCombinedNode() {
695        if (getNumberOfConfigurationsInternal() < 1) {
696            if (getLogger().isDebugEnabled()) {
697                getLogger().debug("No configurations defined for " + this);
698            }
699            return EMPTY_ROOT;
700        }
701        final Iterator<ConfigData> it = configurations.iterator();
702        ImmutableNode node = it.next().getTransformedRoot();
703        while (it.hasNext()) {
704            node = nodeCombiner.combine(node, it.next().getTransformedRoot());
705        }
706        if (getLogger().isDebugEnabled()) {
707            final ByteArrayOutputStream os = new ByteArrayOutputStream();
708            final PrintStream stream = new PrintStream(os);
709            TreeUtils.printTree(stream, node);
710            getLogger().debug(os.toString());
711        }
712        return node;
713    }
714
715    /**
716     * Determines the configurations to which the specified node belongs. This is done by inspecting the nodes structures of
717     * all child configurations.
718     *
719     * @param node the node
720     * @return a set with the owning configurations
721     */
722    private Set<Configuration> findSourceConfigurations(final ImmutableNode node) {
723        final Set<Configuration> result = new HashSet<>();
724        final FindNodeVisitor<ImmutableNode> visitor = new FindNodeVisitor<>(node);
725
726        configurations.forEach(cd -> {
727            NodeTreeWalker.INSTANCE.walkBFS(cd.getRootNode(), visitor, getModel().getNodeHandler());
728            if (visitor.isFound()) {
729                result.add(cd.getConfiguration());
730                visitor.reset();
731            }
732        });
733
734        return result;
735    }
736
737    /**
738     * Registers this combined configuration as listener at the given child configuration.
739     *
740     * @param configuration the child configuration
741     */
742    private void registerListenerAt(final Configuration configuration) {
743        if (configuration instanceof EventSource) {
744            ((EventSource) configuration).addEventListener(ConfigurationEvent.ANY, this);
745        }
746    }
747
748    /**
749     * Removes this combined configuration as listener from the given child configuration.
750     *
751     * @param configuration the child configuration
752     */
753    private void unregisterListenerAt(final Configuration configuration) {
754        if (configuration instanceof EventSource) {
755            ((EventSource) configuration).removeEventListener(ConfigurationEvent.ANY, this);
756        }
757    }
758
759    /**
760     * Removes this combined configuration as listener from all child configurations. This method is called on a clear()
761     * operation.
762     */
763    private void unregisterListenerAtChildren() {
764        if (configurations != null) {
765            configurations.forEach(child -> unregisterListenerAt(child.getConfiguration()));
766        }
767    }
768
769    /**
770     * Gets the number of child configurations in this combined configuration. The internal list of child configurations
771     * is accessed without synchronization.
772     *
773     * @return the number of child configurations
774     */
775    private int getNumberOfConfigurationsInternal() {
776        return configurations.size();
777    }
778
779    /**
780     * An internal helper class for storing information about contained configurations.
781     */
782    private final class ConfigData {
783        /** Stores a reference to the configuration. */
784        private final Configuration configuration;
785
786        /** Stores the name under which the configuration is stored. */
787        private final String name;
788
789        /** Stores the at information as path of nodes. */
790        private final Collection<String> atPath;
791
792        /** Stores the at string. */
793        private final String at;
794
795        /** Stores the root node for this child configuration. */
796        private ImmutableNode rootNode;
797
798        /**
799         * Creates a new instance of {@code ConfigData} and initializes it.
800         *
801         * @param config the configuration
802         * @param n the name
803         * @param at the at position
804         */
805        public ConfigData(final Configuration config, final String n, final String at) {
806            configuration = config;
807            name = n;
808            atPath = parseAt(at);
809            this.at = at;
810        }
811
812        /**
813         * Gets the stored configuration.
814         *
815         * @return the configuration
816         */
817        public Configuration getConfiguration() {
818            return configuration;
819        }
820
821        /**
822         * Gets the configuration's name.
823         *
824         * @return the name
825         */
826        public String getName() {
827            return name;
828        }
829
830        /**
831         * Gets the at position of this configuration.
832         *
833         * @return the at position
834         */
835        public String getAt() {
836            return at;
837        }
838
839        /**
840         * Gets the root node for this child configuration.
841         *
842         * @return the root node of this child configuration
843         * @since 1.5
844         */
845        public ImmutableNode getRootNode() {
846            return rootNode;
847        }
848
849        /**
850         * Gets the transformed root node of the stored configuration. The term &quot;transformed&quot; means that an
851         * eventually defined at path has been applied.
852         *
853         * @return the transformed root node
854         */
855        public ImmutableNode getTransformedRoot() {
856            final ImmutableNode configRoot = getRootNodeOfConfiguration();
857            return atPath == null ? configRoot : prependAtPath(configRoot);
858        }
859
860        /**
861         * Prepends the at path to the given node.
862         *
863         * @param node the root node of the represented configuration
864         * @return the new root node including the at path
865         */
866        private ImmutableNode prependAtPath(final ImmutableNode node) {
867            final ImmutableNode.Builder pathBuilder = new ImmutableNode.Builder();
868            final Iterator<String> pathIterator = atPath.iterator();
869            prependAtPathComponent(pathBuilder, pathIterator.next(), pathIterator, node);
870            return new ImmutableNode.Builder(1).addChild(pathBuilder.create()).create();
871        }
872
873        /**
874         * Handles a single component of the at path. A corresponding node is created and added to the hierarchical path to the
875         * original root node of the configuration.
876         *
877         * @param builder the current node builder object
878         * @param currentComponent the name of the current path component
879         * @param components an iterator with all components of the at path
880         * @param orgRoot the original root node of the wrapped configuration
881         */
882        private void prependAtPathComponent(final ImmutableNode.Builder builder, final String currentComponent, final Iterator<String> components,
883            final ImmutableNode orgRoot) {
884            builder.name(currentComponent);
885            if (components.hasNext()) {
886                final ImmutableNode.Builder childBuilder = new ImmutableNode.Builder();
887                prependAtPathComponent(childBuilder, components.next(), components, orgRoot);
888                builder.addChild(childBuilder.create());
889            } else {
890                builder.addChildren(orgRoot.getChildren());
891                builder.addAttributes(orgRoot.getAttributes());
892                builder.value(orgRoot.getValue());
893            }
894        }
895
896        /**
897         * Obtains the root node of the wrapped configuration. If necessary, a hierarchical representation of the configuration
898         * has to be created first.
899         *
900         * @return the root node of the associated configuration
901         */
902        private ImmutableNode getRootNodeOfConfiguration() {
903            getConfiguration().lock(LockMode.READ);
904            try {
905                final ImmutableNode root = ConfigurationUtils.convertToHierarchical(getConfiguration(), conversionExpressionEngine).getNodeModel()
906                    .getInMemoryRepresentation();
907                rootNode = root;
908                return root;
909            } finally {
910                getConfiguration().unlock(LockMode.READ);
911            }
912        }
913
914        /**
915         * Splits the at path into its components.
916         *
917         * @param at the at string
918         * @return a collection with the names of the single components
919         */
920        private Collection<String> parseAt(final String at) {
921            if (StringUtils.isEmpty(at)) {
922                return null;
923            }
924
925            final Collection<String> result = new ArrayList<>();
926            final DefaultConfigurationKey.KeyIterator it = new DefaultConfigurationKey(AT_ENGINE, at).iterator();
927            while (it.hasNext()) {
928                result.add(it.nextKey());
929            }
930            return result;
931        }
932    }
933}