    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}