Package org.apache.commons.configuration

Source Code of org.apache.commons.configuration.CombinedConfiguration

/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements.  See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License.  You may obtain a copy of the License at
*
*     http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.commons.configuration;

import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.configuration.event.ConfigurationEvent;
import org.apache.commons.configuration.event.EventListener;
import org.apache.commons.configuration.event.EventSource;
import org.apache.commons.configuration.event.EventType;
import org.apache.commons.configuration.ex.ConfigurationRuntimeException;
import org.apache.commons.configuration.sync.LockMode;
import org.apache.commons.configuration.tree.DefaultConfigurationKey;
import org.apache.commons.configuration.tree.DefaultExpressionEngine;
import org.apache.commons.configuration.tree.ExpressionEngine;
import org.apache.commons.configuration.tree.ImmutableNode;
import org.apache.commons.configuration.tree.NodeCombiner;
import org.apache.commons.configuration.tree.NodeTreeWalker;
import org.apache.commons.configuration.tree.QueryResult;
import org.apache.commons.configuration.tree.TreeUtils;
import org.apache.commons.configuration.tree.UnionCombiner;

/**
* <p>
* A hierarchical composite configuration class.
* </p>
* <p>
* This class maintains a list of configuration objects, which can be added
* using the diverse {@code addConfiguration()} methods. After that the
* configurations can be accessed either by name (if one was provided when the
* configuration was added) or by index. For the whole set of managed
* configurations a logical node structure is constructed. For this purpose a
* {@link org.apache.commons.configuration.tree.NodeCombiner NodeCombiner}
* object can be set. This makes it possible to specify different algorithms for
* the combination process.
* </p>
* <p>
* The big advantage of this class is that it creates a truly hierarchical
* structure of all the properties stored in the contained configurations - even
* if some of them are no hierarchical configurations per se. So all enhanced
* features provided by a hierarchical configuration (e.g. choosing an
* expression engine) are applicable.
* </p>
* <p>
* The class works by registering itself as an event listener at all added
* configurations. So it gets notified whenever one of these configurations is
* changed and can invalidate its internal node structure. The next time a
* property is accessed the node structure will be re-constructed using the
* current state of the managed configurations. Note that, depending on the used
* {@code NodeCombiner}, this may be a complex operation.
* </p>
* <p>
* Because of the way a {@code CombinedConfiguration} is working it has more or
* less view character: it provides a logic view on the configurations it
* contains. In this constellation not all methods defined for hierarchical
* configurations - especially methods that update the stored properties - can
* be implemented in a consistent manner. Using such methods (like
* {@code addProperty()}, or {@code clearProperty()} on a
* {@code CombinedConfiguration} is not strictly forbidden, however, depending
* on the current {@link NodeCombiner} and the involved properties, the results
* may be different than expected. Some examples may illustrate this:
* </p>
* <p>
* <ul>
* <li>Imagine a {@code CombinedConfiguration} <em>cc</em> containing two child
* configurations with the following content:
* <dl>
* <dt>user.properties</dt>
* <dd>
*
* <pre>
* gui.background = blue
* gui.position = (10, 10, 400, 200)
* </pre>
*
* </dd>
* <dt>default.properties</dt>
* <dd>
*
* <pre>
* gui.background = black
* gui.foreground = white
* home.dir = /data
* </pre>
*
* </dd>
* </dl>
* As a {@code NodeCombiner} a
* {@link org.apache.commons.configuration.tree.OverrideCombiner
* OverrideCombiner} is used. This combiner will ensure that defined user
* settings take precedence over the default values. If the resulting
* {@code CombinedConfiguration} is queried for the background color,
* {@code blue} will be returned because this value is defined in
* {@code user.properties}. Now consider what happens if the key
* {@code gui.background} is removed from the {@code CombinedConfiguration}:
*
* <pre>
* cc.clearProperty(&quot;gui.background&quot;);
* </pre>
*
* Will a {@code cc.containsKey("gui.background")} now return <b>false</b>? No,
* it won't! The {@code clearProperty()} operation is executed on the node set
* of the combined configuration, which was constructed from the nodes of the
* two child configurations. It causes the value of the <em>background</em> node
* to be cleared, which is also part of the first child configuration. This
* modification of one of its child configurations causes the
* {@code CombinedConfiguration} to be re-constructed. This time the
* {@code OverrideCombiner} cannot find a {@code gui.background} property in the
* first child configuration, but it finds one in the second, and adds it to the
* resulting combined configuration. So the property is still present (with a
* different value now).</li>
* <li>{@code addProperty()} can also be problematic: Most node combiners use
* special view nodes for linking parts of the original configurations' data
* together. If new properties are added to such a special node, they do not
* belong to any of the managed configurations and thus hang in the air. Using
* the same configurations as in the last example, the statement
*
* <pre>
* addProperty(&quot;database.user&quot;, &quot;scott&quot;);
* </pre>
*
* would cause such a hanging property. If now one of the child configurations
* is changed and the {@code CombinedConfiguration} is re-constructed, this
* property will disappear! (Add operations are not problematic if they result
* in a child configuration being updated. For instance an
* {@code addProperty("home.url", "localhost");} will alter the second child
* configuration - because the prefix <em>home</em> is here already present;
* when the {@code CombinedConfiguration} is re-constructed, this change is
* taken into account.)</li>
* </ul>
* Because of such problems it is recommended to perform updates only on the
* managed child configurations.
* </p>
* <p>
* Whenever the node structure of a {@code CombinedConfiguration} becomes
* invalid (either because one of the contained configurations was modified or
* because the {@code invalidate()} method was directly called) an event is
* generated. So this can be detected by interested event listeners. This also
* makes it possible to add a combined configuration into another one.
* </p>
* <p>
* Notes about thread-safety: This configuration implementation uses a
* {@code Synchronizer} object to protect instances against concurrent access.
* The concrete {@code Synchronizer} implementation used determines whether an
* instance of this class is thread-safe or not. In contrast to other
* implementations derived from {@link BaseHierarchicalConfiguration},
* thread-safety is an issue here because the nodes structure used by this
* configuration has to be constructed dynamically when a child configuration is
* changed. Therefore, when multiple threads are involved which also manipulate
* one of the child configurations, a proper {@code Synchronizer} object should
* be set. Note that the {@code Synchronizer} objects used by the child
* configurations do not really matter. Because immutable in-memory nodes
* structures are used for them there is no danger that updates on child
* configurations could interfere with read operations on the combined
* configuration.
* </p>
*
* @since 1.3
* @version $Id: CombinedConfiguration.java 1609801 2014-07-11 20:23:02Z oheger $
*/
public class CombinedConfiguration extends BaseHierarchicalConfiguration implements
        EventListener<ConfigurationEvent>, Cloneable
{
    /**
     * Constant for the event type fired when the internal node structure of a
     * combined configuration becomes invalid.
     *
     * @since 2.0
     */
    public static final EventType<ConfigurationEvent> COMBINED_INVALIDATE =
            new EventType<ConfigurationEvent>(ConfigurationEvent.ANY,
                    "COMBINED_INVALIDATE");

    /**
     * The serial version ID.
     */
    private static final long serialVersionUID = 8338574525528692307L;

    /** Constant for the expression engine for parsing the at path. */
    private static final DefaultExpressionEngine AT_ENGINE = DefaultExpressionEngine.INSTANCE;

    /** Constant for the default node combiner. */
    private static final NodeCombiner DEFAULT_COMBINER = new UnionCombiner();

    /** Constant for a root node for an empty configuration. */
    private static final ImmutableNode EMPTY_ROOT = new ImmutableNode.Builder()
            .create();

    /** Stores the combiner. */
    private NodeCombiner nodeCombiner;

    /** Stores a list with the contained configurations. */
    private List<ConfigData> configurations;

    /** Stores a map with the named configurations. */
    private Map<String, Configuration> namedConfigurations;

    /**
     * An expression engine used for converting child configurations to
     * hierarchical ones.
     */
    private ExpressionEngine conversionExpressionEngine;

    /** A flag whether this configuration is up-to-date. */
    private boolean upToDate;

    /**
     * Creates a new instance of {@code CombinedConfiguration} and
     * initializes the combiner to be used.
     *
     * @param comb the node combiner (can be <b>null</b>, then a union combiner
     * is used as default)
     */
    public CombinedConfiguration(NodeCombiner comb)
    {
        nodeCombiner = (comb != null) ? comb : DEFAULT_COMBINER;
        initChildCollections();
    }

    /**
     * Creates a new instance of {@code CombinedConfiguration} that uses
     * a union combiner.
     *
     * @see org.apache.commons.configuration.tree.UnionCombiner
     */
    public CombinedConfiguration()
    {
        this(null);
    }

    /**
     * Returns the node combiner that is used for creating the combined node
     * structure.
     *
     * @return the node combiner
     */
    public NodeCombiner getNodeCombiner()
    {
        beginRead(true);
        try
        {
            return nodeCombiner;
        }
        finally
        {
            endRead();
        }
    }

    /**
     * Sets the node combiner. This object will be used when the combined node
     * structure is to be constructed. It must not be <b>null</b>, otherwise an
     * {@code IllegalArgumentException} exception is thrown. Changing the
     * node combiner causes an invalidation of this combined configuration, so
     * that the new combiner immediately takes effect.
     *
     * @param nodeCombiner the node combiner
     */
    public void setNodeCombiner(NodeCombiner nodeCombiner)
    {
        if (nodeCombiner == null)
        {
            throw new IllegalArgumentException(
                    "Node combiner must not be null!");
        }

        beginWrite(true);
        try
        {
            this.nodeCombiner = nodeCombiner;
            invalidateInternal();
        }
        finally
        {
            endWrite();
        }
    }

    /**
     * Returns the {@code ExpressionEngine} for converting flat child
     * configurations to hierarchical ones.
     *
     * @return the conversion expression engine
     * @since 1.6
     */
    public ExpressionEngine getConversionExpressionEngine()
    {
        beginRead(true);
        try
        {
            return conversionExpressionEngine;
        }
        finally
        {
            endRead();
        }
    }

    /**
     * Sets the {@code ExpressionEngine} for converting flat child
     * configurations to hierarchical ones. When constructing the root node for
     * this combined configuration the properties of all child configurations
     * must be combined to a single hierarchical node structure. In this
     * process, non hierarchical configurations are converted to hierarchical
     * ones first. This can be problematic if a child configuration contains
     * keys that are no compatible with the default expression engine used by
     * hierarchical configurations. Therefore it is possible to specify a
     * specific expression engine to be used for this purpose.
     *
     * @param conversionExpressionEngine the conversion expression engine
     * @see ConfigurationUtils#convertToHierarchical(Configuration, ExpressionEngine)
     * @since 1.6
     */
    public void setConversionExpressionEngine(
            ExpressionEngine conversionExpressionEngine)
    {
        beginWrite(true);
        try
        {
            this.conversionExpressionEngine = conversionExpressionEngine;
        }
        finally
        {
            endWrite();
        }
    }

    /**
     * Adds a new configuration to this combined configuration. It is possible
     * (but not mandatory) to give the new configuration a name. This name must
     * be unique, otherwise a {@code ConfigurationRuntimeException} will
     * be thrown. With the optional {@code at} argument you can specify
     * where in the resulting node structure the content of the added
     * configuration should appear. This is a string that uses dots as property
     * delimiters (independent on the current expression engine). For instance
     * if you pass in the string {@code "database.tables"},
     * all properties of the added configuration will occur in this branch.
     *
     * @param config the configuration to add (must not be <b>null</b>)
     * @param name the name of this configuration (can be <b>null</b>)
     * @param at the position of this configuration in the combined tree (can be
     * <b>null</b>)
     */
    public void addConfiguration(Configuration config, String name,
            String at)
    {
        if (config == null)
        {
            throw new IllegalArgumentException(
                    "Added configuration must not be null!");
        }

        beginWrite(true);
        try
        {
            if (name != null && namedConfigurations.containsKey(name))
            {
                throw new ConfigurationRuntimeException(
                        "A configuration with the name '"
                                + name
                                + "' already exists in this combined configuration!");
            }

            ConfigData cd = new ConfigData(config, name, at);
            if (getLogger().isDebugEnabled())
            {
                getLogger()
                        .debug("Adding configuration " + config + " with name "
                                + name);
            }
            configurations.add(cd);
            if (name != null)
            {
                namedConfigurations.put(name, config);
            }

            invalidateInternal();
        }
        finally
        {
            endWrite();
        }
        registerListenerAt(config);
    }

    /**
     * Adds a new configuration to this combined configuration with an optional
     * name. The new configuration's properties will be added under the root of
     * the combined node structure.
     *
     * @param config the configuration to add (must not be <b>null</b>)
     * @param name the name of this configuration (can be <b>null</b>)
     */
    public void addConfiguration(Configuration config, String name)
    {
        addConfiguration(config, name, null);
    }

    /**
     * Adds a new configuration to this combined configuration. The new
     * configuration is not given a name. Its properties will be added under the
     * root of the combined node structure.
     *
     * @param config the configuration to add (must not be <b>null</b>)
     */
    public void addConfiguration(Configuration config)
    {
        addConfiguration(config, null, null);
    }

    /**
     * Returns the number of configurations that are contained in this combined
     * configuration.
     *
     * @return the number of contained configurations
     */
    public int getNumberOfConfigurations()
    {
        beginRead(true);
        try
        {
            return getNumberOfConfigurationsInternal();
        }
        finally
        {
            endRead();
        }
    }

    /**
     * Returns the configuration at the specified index. The contained
     * configurations are numbered in the order they were added to this combined
     * configuration. The index of the first configuration is 0.
     *
     * @param index the index
     * @return the configuration at this index
     */
    public Configuration getConfiguration(int index)
    {
        beginRead(true);
        try
        {
            ConfigData cd = configurations.get(index);
            return cd.getConfiguration();
        }
        finally
        {
            endRead();
        }
    }

    /**
     * Returns the configuration with the given name. This can be <b>null</b>
     * if no such configuration exists.
     *
     * @param name the name of the configuration
     * @return the configuration with this name
     */
    public Configuration getConfiguration(String name)
    {
        beginRead(true);
        try
        {
            return namedConfigurations.get(name);
        }
        finally
        {
            endRead();
        }
    }

    /**
     * Returns a List of all the configurations that have been added.
     * @return A List of all the configurations.
     * @since 1.7
     */
    public List<Configuration> getConfigurations()
    {
        beginRead(true);
        try
        {
            List<Configuration> list =
                    new ArrayList<Configuration>(getNumberOfConfigurationsInternal());
            for (ConfigData cd : configurations)
            {
                list.add(cd.getConfiguration());
            }
            return list;
        }
        finally
        {
            endRead();
        }
    }

    /**
     * Returns a List of the names of all the configurations that have been
     * added in the order they were added. A NULL value will be present in
     * the list for each configuration that was added without a name.
     * @return A List of all the configuration names.
     * @since 1.7
     */
    public List<String> getConfigurationNameList()
    {
        beginRead(true);
        try
        {
            List<String> list = new ArrayList<String>(getNumberOfConfigurationsInternal());
            for (ConfigData cd : configurations)
            {
                list.add(cd.getName());
            }
            return list;
        }
        finally
        {
            endRead();
        }
    }

    /**
     * Removes the specified configuration from this combined configuration.
     *
     * @param config the configuration to be removed
     * @return a flag whether this configuration was found and could be removed
     */
    public boolean removeConfiguration(Configuration config)
    {
        for (int index = 0; index < getNumberOfConfigurations(); index++)
        {
            if (configurations.get(index).getConfiguration() == config)
            {
                removeConfigurationAt(index);
                return true;
            }
        }

        return false;
    }

    /**
     * Removes the configuration at the specified index.
     *
     * @param index the index
     * @return the removed configuration
     */
    public Configuration removeConfigurationAt(int index)
    {
        ConfigData cd = configurations.remove(index);
        if (cd.getName() != null)
        {
            namedConfigurations.remove(cd.getName());
        }
        unregisterListenerAt(cd.getConfiguration());
        invalidateInternal();
        return cd.getConfiguration();
    }

    /**
     * Removes the configuration with the specified name.
     *
     * @param name the name of the configuration to be removed
     * @return the removed configuration (<b>null</b> if this configuration
     * was not found)
     */
    public Configuration removeConfiguration(String name)
    {
        Configuration conf = getConfiguration(name);
        if (conf != null)
        {
            removeConfiguration(conf);
        }
        return conf;
    }

    /**
     * Returns a set with the names of all configurations contained in this
     * combined configuration. Of course here are only these configurations
     * listed, for which a name was specified when they were added.
     *
     * @return a set with the names of the contained configurations (never
     * <b>null</b>)
     */
    public Set<String> getConfigurationNames()
    {
        beginRead(true);
        try
        {
            return namedConfigurations.keySet();
        }
        finally
        {
            endRead();
        }
    }

    /**
     * Invalidates this combined configuration. This means that the next time a
     * property is accessed the combined node structure must be re-constructed.
     * Invalidation of a combined configuration also means that an event of type
     * {@code EVENT_COMBINED_INVALIDATE} is fired. Note that while other
     * events most times appear twice (once before and once after an update),
     * this event is only fired once (after update).
     */
    public void invalidate()
    {
        beginWrite(true);
        try
        {
            invalidateInternal();
        }
        finally
        {
            endWrite();
        }
    }

    /**
     * Event listener call back for configuration update events. This method is
     * called whenever one of the contained configurations was modified. It
     * invalidates this combined configuration.
     *
     * @param event the update event
     */
    @Override
    public void onEvent(ConfigurationEvent event)
    {
        if (event.isBeforeUpdate())
        {
            invalidate();
        }
    }

    /**
     * Clears this configuration. All contained configurations will be removed.
     */
    @Override
    protected void clearInternal()
    {
        unregisterListenerAtChildren();
        initChildCollections();
        invalidateInternal();
    }

    /**
     * Returns a copy of this object. This implementation performs a deep clone,
     * i.e. all contained configurations will be cloned, too. For this to work,
     * all contained configurations must be cloneable. Registered event
     * listeners won't be cloned. The clone will use the same node combiner than
     * the original.
     *
     * @return the copied object
     */
    @Override
    public Object clone()
    {
        beginRead(false);
        try
        {
            CombinedConfiguration copy = (CombinedConfiguration) super.clone();
            copy.initChildCollections();
            for (ConfigData cd : configurations)
            {
                copy.addConfiguration(ConfigurationUtils.cloneConfiguration(cd
                        .getConfiguration()), cd.getName(), cd.getAt());
            }

            return copy;
        }
        finally
        {
            endRead();
        }
    }

    /**
     * Returns the configuration source, in which the specified key is defined.
     * This method will determine the configuration node that is identified by
     * the given key. The following constellations are possible:
     * <ul>
     * <li>If no node object is found for this key, <b>null</b> is returned.</li>
     * <li>If the key maps to multiple nodes belonging to different
     * configuration sources, a {@code IllegalArgumentException} is
     * thrown (in this case no unique source can be determined).</li>
     * <li>If exactly one node is found for the key, the (child) configuration
     * object, to which the node belongs is determined and returned.</li>
     * <li>For keys that have been added directly to this combined
     * configuration and that do not belong to the namespaces defined by
     * existing child configurations this configuration will be returned.</li>
     * </ul>
     *
     * @param key the key of a configuration property
     * @return the configuration, to which this property belongs or <b>null</b>
     * if the key cannot be resolved
     * @throws IllegalArgumentException if the key maps to multiple properties
     * and the source cannot be determined, or if the key is <b>null</b>
     * @since 1.5
     */
    public Configuration getSource(String key)
    {
        if (key == null)
        {
            throw new IllegalArgumentException("Key must not be null!");
        }

        Set<Configuration> sources = getSources(key);
        if (sources.isEmpty())
        {
            return null;
        }
        Iterator<Configuration> iterator = sources.iterator();
        Configuration source = iterator.next();
        if (iterator.hasNext())
        {
            throw new IllegalArgumentException("The key " + key
                    + " is defined by multiple sources!");
        }
        return source;
    }

    /**
     * Returns a set with the configuration sources, in which the specified key
     * is defined. This method determines the configuration nodes that are
     * identified by the given key. It then determines the configuration sources
     * to which these nodes belong and adds them to the result set. Note the
     * following points:
     * <ul>
     * <li>If no node object is found for this key, an empty set is returned.</li>
     * <li>For keys that have been added directly to this combined configuration
     * and that do not belong to the namespaces defined by existing child
     * configurations this combined configuration is contained in the result
     * set.</li>
     * </ul>
     *
     * @param key the key of a configuration property
     * @return a set with the configuration sources, which contain this property
     * @since 2.0
     */
    public Set<Configuration> getSources(String key)
    {
        beginRead(false);
        try
        {
            List<QueryResult<ImmutableNode>> results = fetchNodeList(key);
            Set<Configuration> sources = new HashSet<Configuration>();

            for (QueryResult<ImmutableNode> result : results)
            {
                Set<Configuration> resultSources =
                        findSourceConfigurations(result.getNode());
                if (resultSources.isEmpty())
                {
                    // key must be defined in combined configuration
                    sources.add(this);
                }
                else
                {
                    sources.addAll(resultSources);
                }
            }

            return sources;
        }
        finally
        {
            endRead();
        }
    }

    /**
     * {@inheritDoc} This implementation checks whether a combined root node
     * is available. If not, it is constructed by requesting a write lock.
     */
    @Override
    protected void beginRead(boolean optimize)
    {
        if (optimize)
        {
            // just need a lock, don't construct configuration
            super.beginRead(true);
            return;
        }

        boolean lockObtained = false;
        do
        {
            super.beginRead(false);
            if (isUpToDate())
            {
                lockObtained = true;
            }
            else
            {
                // release read lock and try to obtain a write lock
                endRead();
                beginWrite(false); // this constructs the root node
                endWrite();
            }
        } while (!lockObtained);
    }

    /**
     * {@inheritDoc} This implementation checks whether a combined root node
     * is available. If not, it is constructed now.
     */
    @Override
    protected void beginWrite(boolean optimize)
    {
        super.beginWrite(true);
        if(optimize)
        {
            // just need a lock, don't construct configuration
            return;
        }

        try
        {
            if (!isUpToDate())
            {
                getSubConfigurationParentModel().replaceRoot(
                        constructCombinedNode(), this);
                upToDate = true;
            }
        }
        catch (RuntimeException rex)
        {
            endWrite();
            throw rex;
        }
    }

    /**
     * Returns a flag whether this configuration has been invalidated. This
     * means that the combined nodes structure has to be rebuilt before the
     * configuration can be accessed.
     *
     * @return a flag whether this configuration is invalid
     */
    private boolean isUpToDate()
    {
        return upToDate;
    }

    /**
     * Marks this configuration as invalid. This means that the next access
     * re-creates the root node. An invalidate event is also fired. Note:
     * This implementation expects that an exclusive (write) lock is held on
     * this instance.
     */
    private void invalidateInternal()
    {
        upToDate = false;
        fireEvent(COMBINED_INVALIDATE, null, null, false);
    }

    /**
     * Initializes internal data structures for storing information about
     * child configurations.
     */
    private void initChildCollections()
    {
        configurations = new ArrayList<ConfigData>();
        namedConfigurations = new HashMap<String, Configuration>();
    }

    /**
     * Creates the root node of this combined configuration.
     *
     * @return the combined root node
     */
    private ImmutableNode constructCombinedNode()
    {
        if (getNumberOfConfigurationsInternal() < 1)
        {
            if (getLogger().isDebugEnabled())
            {
                getLogger().debug("No configurations defined for " + this);
            }
            return EMPTY_ROOT;
        }

        else
        {
            Iterator<ConfigData> it = configurations.iterator();
            ImmutableNode node = it.next().getTransformedRoot();
            while (it.hasNext())
            {
                node = nodeCombiner.combine(node,
                        it.next().getTransformedRoot());
            }
            if (getLogger().isDebugEnabled())
            {
                ByteArrayOutputStream os = new ByteArrayOutputStream();
                PrintStream stream = new PrintStream(os);
                TreeUtils.printTree(stream, node);
                getLogger().debug(os.toString());
            }
            return node;
        }
    }

    /**
     * Determines the configurations to which the specified node belongs. This
     * is done by inspecting the nodes structures of all child configurations.
     *
     * @param node the node
     * @return a set with the owning configurations
     */
    private Set<Configuration> findSourceConfigurations(ImmutableNode node)
    {
        Set<Configuration> result = new HashSet<Configuration>();
        FindNodeVisitor<ImmutableNode> visitor =
                new FindNodeVisitor<ImmutableNode>(node);

        for (ConfigData cd : configurations)
        {
            NodeTreeWalker.INSTANCE.walkBFS(cd.getRootNode(), visitor,
                    getModel().getNodeHandler());
            if (visitor.isFound())
            {
                result.add(cd.getConfiguration());
                visitor.reset();
            }
        }

        return result;
    }

    /**
     * Registers this combined configuration as listener at the given child
     * configuration.
     *
     * @param configuration the child configuration
     */
    private void registerListenerAt(Configuration configuration)
    {
        if (configuration instanceof EventSource)
        {
            ((EventSource) configuration).addEventListener(
                    ConfigurationEvent.ANY, this);
        }
    }

    /**
     * Removes this combined configuration as listener from the given child
     * configuration.
     *
     * @param configuration the child configuration
     */
    private void unregisterListenerAt(Configuration configuration)
    {
        if (configuration instanceof EventSource)
        {
            ((EventSource) configuration).removeEventListener(
                    ConfigurationEvent.ANY, this);
        }
    }

    /**
     * Removes this combined configuration as listener from all child
     * configurations. This method is called on a clear() operation.
     */
    private void unregisterListenerAtChildren()
    {
        if (configurations != null)
        {
            for (ConfigData child : configurations)
            {
                unregisterListenerAt(child.getConfiguration());
            }
        }
    }

    /**
     * Returns the number of child configurations in this combined
     * configuration. The internal list of child configurations is accessed
     * without synchronization.
     *
     * @return the number of child configurations
     */
    private int getNumberOfConfigurationsInternal()
    {
        return configurations.size();
    }

    /**
     * An internal helper class for storing information about contained
     * configurations.
     */
    private class ConfigData
    {
        /** Stores a reference to the configuration. */
        private final Configuration configuration;

        /** Stores the name under which the configuration is stored. */
        private final String name;

        /** Stores the at information as path of nodes. */
        private final Collection<String> atPath;

        /** Stores the at string.*/
        private final String at;

        /** Stores the root node for this child configuration.*/
        private ImmutableNode rootNode;

        /**
         * Creates a new instance of {@code ConfigData} and initializes
         * it.
         *
         * @param config the configuration
         * @param n the name
         * @param at the at position
         */
        public ConfigData(Configuration config, String n, String at)
        {
            configuration = config;
            name = n;
            atPath = parseAt(at);
            this.at = at;
        }

        /**
         * Returns the stored configuration.
         *
         * @return the configuration
         */
        public Configuration getConfiguration()
        {
            return configuration;
        }

        /**
         * Returns the configuration's name.
         *
         * @return the name
         */
        public String getName()
        {
            return name;
        }

        /**
         * Returns the at position of this configuration.
         *
         * @return the at position
         */
        public String getAt()
        {
            return at;
        }

        /**
         * Returns the root node for this child configuration.
         *
         * @return the root node of this child configuration
         * @since 1.5
         */
        public ImmutableNode getRootNode()
        {
            return rootNode;
        }

        /**
         * Returns the transformed root node of the stored configuration. The
         * term &quot;transformed&quot; means that an eventually defined at path
         * has been applied.
         *
         * @return the transformed root node
         */
        public ImmutableNode getTransformedRoot()
        {
            ImmutableNode configRoot = getRootNodeOfConfiguration();
            return (atPath == null) ? configRoot : prependAtPath(configRoot);
        }

        /**
         * Prepends the at path to the given node.
         *
         * @param node the root node of the represented configuration
         * @return the new root node including the at path
         */
        private ImmutableNode prependAtPath(ImmutableNode node)
        {
            ImmutableNode.Builder pathBuilder = new ImmutableNode.Builder();
            Iterator<String> pathIterator = atPath.iterator();
            prependAtPathComponent(pathBuilder, pathIterator.next(),
                    pathIterator, node);
            return new ImmutableNode.Builder(1).addChild(pathBuilder.create())
                    .create();
        }

        /**
         * Handles a single component of the at path. A corresponding node is
         * created and added to the hierarchical path to the original root node
         * of the configuration.
         *
         * @param builder the current node builder object
         * @param currentComponent the name of the current path component
         * @param components an iterator with all components of the at path
         * @param orgRoot the original root node of the wrapped configuration
         */
        private void prependAtPathComponent(ImmutableNode.Builder builder,
                String currentComponent, Iterator<String> components,
                ImmutableNode orgRoot)
        {
            builder.name(currentComponent);
            if (components.hasNext())
            {
                ImmutableNode.Builder childBuilder =
                        new ImmutableNode.Builder();
                prependAtPathComponent(childBuilder, components.next(),
                        components, orgRoot);
                builder.addChild(childBuilder.create());
            }
            else
            {
                builder.addChildren(orgRoot.getChildren());
                builder.addAttributes(orgRoot.getAttributes());
                builder.value(orgRoot.getValue());
            }
        }

        /**
         * Obtains the root node of the wrapped configuration. If necessary, a
         * hierarchical representation of the configuration has to be created
         * first.
         *
         * @return the root node of the associated configuration
         */
        private ImmutableNode getRootNodeOfConfiguration()
        {
            getConfiguration().lock(LockMode.READ);
            try
            {
                ImmutableNode root =
                        ConfigurationUtils
                                .convertToHierarchical(getConfiguration(),
                                        conversionExpressionEngine).getNodeModel()
                                .getInMemoryRepresentation();
                rootNode = root;
                return root;
            }
            finally
            {
                getConfiguration().unlock(LockMode.READ);
            }
        }

        /**
         * Splits the at path into its components.
         *
         * @param at the at string
         * @return a collection with the names of the single components
         */
        private Collection<String> parseAt(String at)
        {
            if (at == null)
            {
                return null;
            }

            Collection<String> result = new ArrayList<String>();
            DefaultConfigurationKey.KeyIterator it = new DefaultConfigurationKey(
                    AT_ENGINE, at).iterator();
            while (it.hasNext())
            {
                result.add(it.nextKey());
            }
            return result;
        }
    }
}
TOP

Related Classes of org.apache.commons.configuration.CombinedConfiguration

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.