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