You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by oh...@apache.org on 2009/08/12 22:21:07 UTC

svn commit: r803697 - in /commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/base: ConfigurationImpl.java SubConfiguration.java

Author: oheger
Date: Wed Aug 12 20:21:06 2009
New Revision: 803697

URL: http://svn.apache.org/viewvc?rev=803697&view=rev
Log:
Implementation classes for the Configuration interface.

Added:
    commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/base/ConfigurationImpl.java   (with props)
    commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/base/SubConfiguration.java   (with props)

Added: commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/base/ConfigurationImpl.java
URL: http://svn.apache.org/viewvc/commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/base/ConfigurationImpl.java?rev=803697&view=auto
==============================================================================
--- commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/base/ConfigurationImpl.java (added)
+++ commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/base/ConfigurationImpl.java Wed Aug 12 20:21:06 2009
@@ -0,0 +1,1050 @@
+/*
+ * 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.configuration2.base;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.Stack;
+
+import org.apache.commons.configuration2.AbstractConfiguration;
+import org.apache.commons.configuration2.PropertyConverter;
+import org.apache.commons.configuration2.event.ConfigurationEvent;
+import org.apache.commons.configuration2.event.ConfigurationListener;
+import org.apache.commons.configuration2.expr.ExpressionEngine;
+import org.apache.commons.configuration2.expr.NodeAddData;
+import org.apache.commons.configuration2.expr.NodeHandler;
+import org.apache.commons.configuration2.expr.NodeList;
+import org.apache.commons.configuration2.expr.NodeVisitor;
+import org.apache.commons.configuration2.expr.NodeVisitorAdapter;
+import org.apache.commons.configuration2.expr.def.DefaultExpressionEngine;
+import org.apache.commons.lang.StringUtils;
+
+/**
+ * <p>
+ * The default implementation of the {@code Configuration} interface.
+ * </p>
+ * <p>
+ * This class implements all the functionality defined by the {@code
+ * Configuration} interface. Therefore it provides a rich API for dealing with
+ * configuration settings (organized in a hierarchical structure).
+ * </p>
+ * <p>
+ * The functionality provided by this class is implemented on top of a
+ * {@link HierarchicalConfigurationSource} object that must be passed to the
+ * constructor. This source object maintains the node structure with the
+ * configuration settings. On these nodes this class implements high-level
+ * operations like interpolation, data conversion, or enhanced queries. The type
+ * of the nodes is controlled by the {@code HierarchicalConfigurationSource}
+ * object and reflected by the generics parameter of this class.
+ * </p>
+ *
+ * @author Commons Configuration team
+ * @version $Id: AbstractHierarchicalConfiguration.java 786440 2009-06-19
+ *          10:35:01Z ebourg $
+ * @since 2.0
+ * @param <T> the type of the nodes this configuration deals with
+ */
+public class ConfigurationImpl<T> extends AbstractConfiguration implements
+        Configuration<T>
+{
+    /**
+     * Constant for the clear tree event.
+     */
+    public static final int EVENT_CLEAR_TREE = 10;
+
+    /**
+     * Constant for the subnode configuration modified event.
+     */
+    public static final int EVENT_SUBNODE_CHANGED = 12;
+
+    /** Stores the default expression engine to be used for new objects. */
+    private static ExpressionEngine defaultExpressionEngine;
+
+    /** Stores the underlying configuration source. */
+    private final HierarchicalConfigurationSource<T> configurationSource;
+
+    /** Stores the expression engine for this instance. */
+    private ExpressionEngine expressionEngine;
+
+    /**
+     * Creates a new instance of {@code AbstractHierarchicalConfiguration} and
+     * initializes it with the given {@code HierarchicalConfigurationSource}.
+     *
+     * @param source the underlying configuration source (must not be
+     *        <b>null</b>)
+     * @throws IllegalArgumentException if the configuration source is
+     *         <b>null</b>
+     */
+    public ConfigurationImpl(HierarchicalConfigurationSource<T> source)
+    {
+        if (source == null)
+        {
+            throw new IllegalArgumentException(
+                    "Configuration source must not be null!");
+        }
+        configurationSource = source;
+    }
+
+    /**
+     * Creates a new instance of {@code AbstractHierarchicalConfiguration}. This
+     * constructor is intended to be used by sub classes that maintain their own
+     * configuration source. Such classes must override the
+     * {@link #getConfigurationSource()} method to return a valid configuration
+     * source.
+     */
+    protected ConfigurationImpl()
+    {
+        configurationSource = null;
+    }
+
+    /**
+     * Returns the {@code HierarchicalConfigurationSource} used by this
+     * configuration.
+     *
+     * @return the underlying {@code HierarchicalConfigurationSource}
+     */
+    public HierarchicalConfigurationSource<T> getConfigurationSource()
+    {
+        return configurationSource;
+    }
+
+    /**
+     * Returns the {@code NodeHandler} used by this configuration. This is a
+     * convenience method that obtains the {@code NodeHandler} from the
+     * associated {@code HierarchicalConfigurationSource}.
+     *
+     * @return the {@code NodeHandler}
+     * @see HierarchicalConfigurationSource#getNodeHandler()
+     */
+    public NodeHandler<T> getNodeHandler()
+    {
+        return getConfigurationSource().getNodeHandler();
+    }
+
+    /**
+     * Returns the default expression engine.
+     *
+     * @return the default expression engine
+     */
+    public static synchronized ExpressionEngine getDefaultExpressionEngine()
+    {
+        if (defaultExpressionEngine == null)
+        {
+            defaultExpressionEngine = new DefaultExpressionEngine();
+        }
+        return defaultExpressionEngine;
+    }
+
+    /**
+     * Sets the default expression engine. This expression engine will be used
+     * if no specific engine was set for an instance. It is shared between all
+     * hierarchical configuration instances. So modifying its properties will
+     * impact all instances, for which no specific engine is set.
+     *
+     * @param engine the new default expression engine
+     */
+    public static synchronized void setDefaultExpressionEngine(
+            ExpressionEngine engine)
+    {
+        if (engine == null)
+        {
+            throw new IllegalArgumentException(
+                    "Default expression engine must not be null!");
+        }
+        defaultExpressionEngine = engine;
+    }
+
+    /**
+     * Returns the expression engine used by this configuration. This method
+     * will never return <b>null</b>; if no specific expression engine was set,
+     * the default expression engine will be returned.
+     *
+     * @return the current expression engine
+     */
+    public ExpressionEngine getExpressionEngine()
+    {
+        return (expressionEngine != null) ? expressionEngine
+                : getDefaultExpressionEngine();
+    }
+
+    /**
+     * Sets the expression engine to be used by this configuration. All property
+     * keys this configuration has to deal with will be interpreted by this
+     * engine.
+     *
+     * @param expressionEngine the new expression engine; can be <b>null</b>,
+     *        then the default expression engine will be used
+     */
+    public void setExpressionEngine(ExpressionEngine expressionEngine)
+    {
+        this.expressionEngine = expressionEngine;
+    }
+
+    /**
+     * Fetches the specified property. This task is delegated to the associated
+     * expression engine.
+     *
+     * @param key the key to be looked up
+     * @return the found value
+     */
+    public Object getProperty(String key)
+    {
+        NodeList<T> nodes = fetchNodeList(key);
+
+        if (nodes.size() == 0)
+        {
+            return null;
+        }
+        else
+        {
+            List<Object> list = new ArrayList<Object>();
+            for (int i = 0; i < nodes.size(); i++)
+            {
+                Object value = nodes.getValue(i, getNodeHandler());
+                if (value != null)
+                {
+                    if (nodes.isAttribute(i) && value instanceof Collection<?>)
+                    {
+                        // there may be multiple values
+                        list.addAll((Collection<?>) value);
+                    }
+                    else
+                    {
+                        list.add(value);
+                    }
+                }
+            }
+
+            if (list.size() < 1)
+            {
+                return null;
+            }
+            else
+            {
+                return (list.size() == 1) ? list.get(0) : list;
+            }
+        }
+    }
+
+    /**
+     * Adds the property with the specified key. This task will be delegated to
+     * the associated <code>ExpressionEngine</code>, so the passed in key must
+     * match the requirements of this implementation.
+     *
+     * @param key the key of the new property
+     * @param obj the value of the new property
+     */
+    @Override
+    protected void addPropertyDirect(String key, Object obj)
+    {
+        NodeAddData<T> data = getExpressionEngine().prepareAdd(
+                getConfigurationSource().getRootNode(), key, getNodeHandler());
+        processNodeAddData(data, obj);
+    }
+
+    /**
+     * Checks if this configuration is empty. Empty means that there are no keys
+     * with any values, though there can be some (empty) nodes.
+     *
+     * @return a flag if this configuration is empty
+     */
+    public boolean isEmpty()
+    {
+        return !nodeDefined(getConfigurationSource().getRootNode());
+    }
+
+    /**
+     * <p>
+     * Returns a hierarchical sub configuration object that wraps the
+     * configuration node specified by the given key. This method provides an
+     * easy means of accessing sub trees of a hierarchical configuration. In the
+     * returned configuration the sub tree can directly be accessed, it becomes
+     * the root node of this configuration. Because of this the passed in key
+     * must select exactly one configuration node; otherwise an
+     * <code>IllegalArgumentException</code> will be thrown.
+     * </p>
+     * <p>
+     * The difference between this method and the
+     * <code>{@link #subset(String)}</code> method is that <code>subset()</code>
+     * supports arbitrary subsets of configuration nodes while
+     * <code>configurationAt()</code> only returns a single sub tree. Please
+     * refer to the documentation of the <code>{@link SubConfiguration}</code>
+     * class to obtain further information about sub configurations and when
+     * they should be used.
+     * </p>
+     * <p>
+     * With the <code>supportUpdate</code> flag the behavior of the returned
+     * <code>SubConfiguration</code> regarding updates of its parent
+     * configuration can be determined. A sub configuration operates on the same
+     * nodes as its parent, so changes at one configuration are normally
+     * directly visible for the other configuration. There are however changes
+     * of the parent configuration, which are not recognized by the sub
+     * configuration per default. An example for this is a reload operation (for
+     * file-based configurations): Here the complete node set of the parent
+     * configuration is replaced, but the sub configuration still references the
+     * old nodes. If such changes should be detected by the sub configuration,
+     * the <code>supportUpdates</code> flag must be set to <b>true</b>. This
+     * causes the sub configuration to reevaluate the key used for its creation
+     * each time it is accessed. This guarantees that the sub configuration
+     * always stays in sync with its key, even if the parent configuration's
+     * data significantly changes. If such a change makes the key invalid -
+     * because it now no longer points to exactly one node -, the sub
+     * configuration is not reconstructed, but keeps its old data. It is then
+     * quasi detached from its parent.
+     * </p>
+     *
+     * @param key the key that selects the sub tree
+     * @param supportUpdates a flag whether the returned sub configuration
+     *        should be able to handle updates of its parent
+     * @return a hierarchical configuration that contains this sub tree
+     * @see SubConfiguration
+     */
+    public SubConfiguration<T> configurationAt(String key,
+            boolean supportUpdates)
+    {
+        NodeList<T> nodes = fetchNodeList(key);
+        if (nodes.size() != 1 || !nodes.isNode(0))
+        {
+            throw new IllegalArgumentException(
+                    "Passed in key must select exactly one node: " + key);
+        }
+        return supportUpdates ? createSubnodeConfiguration(nodes.getNode(0),
+                key) : createSubnodeConfiguration(nodes.getNode(0));
+    }
+
+    /**
+     * Returns a hierarchical sub configuration for the node specified by the
+     * given key. This is a short form for <code>configurationAt(key,
+     * <b>false</b>)</code>.
+     *
+     * @param key the key that selects the sub tree
+     * @return a hierarchical configuration that contains this sub tree
+     * @see SubConfiguration
+     */
+    public SubConfiguration<T> configurationAt(String key)
+    {
+        return configurationAt(key, false);
+    }
+
+    /**
+     * Returns a list of sub configurations for all configuration nodes selected
+     * by the given key. This method will evaluate the passed in key (using the
+     * current <code>ExpressionEngine</code>) and then create a
+     * <code>{@link SubConfiguration}</code> for each returned node (like
+     * <code>{@link #configurationAt(String)}</code> ). This is especially
+     * useful when dealing with list-like structures. As an example consider the
+     * configuration that contains data about database tables and their fields.
+     * If you need access to all fields of a certain table, you can simply do
+     *
+     * <pre>
+     * List&lt;SubConfiguration&lt;T&gt;&gt; fields = config.configurationsAt(&quot;tables.table(0).fields.field&quot;);
+     * for(SubConfiguration sub : fields)
+     * {
+     *     // now the children and attributes of the field node can be
+     *     // directly accessed
+     *     String fieldName = sub.getString(&quot;name&quot;);
+     *     String fieldType = sub.getString(&quot;type&quot;);
+     *     ...
+     * </pre>
+     *
+     * This method also supports a <code>supportUpdates</code> parameter for
+     * making the sub configurations returned aware of structural changes in the
+     * parent configuration. Refer to the documentation of
+     * <code>{@link #configurationAt(String, boolean)}</code> for more details
+     * about the effect of this flag.
+     *
+     * @param key the key for selecting the desired nodes
+     * @param supportUpdates a flag whether the returned sub configurations
+     *        should be able to handle updates of its parent
+     * @return a list with hierarchical configuration objects; each
+     *         configuration represents one of the nodes selected by the passed
+     *         in key
+     */
+    public List<Configuration<T>> configurationsAt(String key,
+            boolean supportUpdates)
+    {
+        NodeList<T> nodes = fetchNodeList(key);
+        List<Configuration<T>> configs = new ArrayList<Configuration<T>>(nodes
+                .size());
+
+        for (int index = 0; index < nodes.size(); index++)
+        {
+            SubConfiguration<T> subConfig;
+            if (supportUpdates)
+            {
+                String subnodeKey = constructPath(nodes.getNode(index));
+                subConfig = createSubnodeConfiguration(nodes.getNode(index),
+                        subnodeKey);
+            }
+            else
+            {
+                subConfig = createSubnodeConfiguration(nodes.getNode(index));
+            }
+            configs.add(subConfig);
+        }
+
+        return configs;
+    }
+
+    /**
+     * Returns a list of sub configurations for all configuration nodes selected
+     * by the given key that are not aware of structural updates of their
+     * parent. This is a short form for
+     * <code>configurationsAt(key, <b>false</b>)</code>.
+     *
+     * @param key the key for selecting the desired nodes
+     * @return a list with hierarchical configuration objects; each
+     *         configuration represents one of the nodes selected by the passed
+     *         in key
+     */
+    public List<Configuration<T>> configurationsAt(String key)
+    {
+        return configurationsAt(key, false);
+    }
+
+    /**
+     * Creates a sub configuration for the specified node. This method is called
+     * by <code>configurationAt()</code> and <code>configurationsAt()</code>.
+     *
+     * @param node the node, for which a sub configuration is to be created
+     * @return the configuration for the given node
+     */
+    protected SubConfiguration<T> createSubnodeConfiguration(T node)
+    {
+        SubConfiguration<T> result = new SubConfiguration<T>(this, node);
+        registerSubnodeConfiguration(result);
+        return result;
+    }
+
+    /**
+     * Creates a new sub configuration for the specified node and sets its
+     * construction key. A sub configuration created this way will be aware of
+     * structural changes of its parent.
+     *
+     * @param node the node, for which a sub configuration is to be created
+     * @param subnodeKey the key used to construct the configuration
+     * @return the configuration for the given node
+     */
+    protected SubConfiguration<T> createSubnodeConfiguration(T node,
+            String subnodeKey)
+    {
+        SubConfiguration<T> result = createSubnodeConfiguration(node);
+        result.setSubnodeKey(subnodeKey);
+        return result;
+    }
+
+    /**
+     * This method is always called when a subnode configuration created from
+     * this configuration has been modified. This implementation transforms the
+     * received event into an event of type <code>EVENT_SUBNODE_CHANGED</code>
+     * and notifies the registered listeners.
+     *
+     * @param event the event describing the change
+     */
+    protected void subnodeConfigurationChanged(ConfigurationEvent event)
+    {
+        fireEvent(EVENT_SUBNODE_CHANGED, null, event, event.isBeforeUpdate());
+    }
+
+    /**
+     * Registers this instance at the given sub configuration. This
+     * implementation will register a change listener, so that modifications of
+     * the sub configuration can be tracked.
+     *
+     * @param config the sub configuration
+     */
+    void registerSubnodeConfiguration(SubConfiguration<T> config)
+    {
+        config.addConfigurationListener(new ConfigurationListener()
+        {
+            public void configurationChanged(ConfigurationEvent event)
+            {
+                subnodeConfigurationChanged(event);
+            }
+        });
+    }
+
+    /**
+     * Determines the path from the given node to the root node. The return
+     * value is the key that uniquely identifies the given node. The associated
+     * expression engine is used for constructing and combining the parts the
+     * key is composed of.
+     *
+     * @param node the node in question (must not be <b>null</b>)
+     * @return a unique key for this node
+     */
+    protected String constructPath(T node)
+    {
+        if (node == null)
+        {
+            return StringUtils.EMPTY;
+        }
+        else
+        {
+            // recursively navigate to the root and construct all paths
+            return getExpressionEngine().uniqueNodeKey(node,
+                    constructPath(getNodeHandler().getParent(node)),
+                    getNodeHandler());
+        }
+    }
+
+    /**
+     * Checks if the specified key is contained in this configuration. Note that
+     * for this configuration the term &quot;contained&quot; means that the key
+     * has an associated value. If there is a node for this key that has no
+     * value but children (either defined or undefined), this method will still
+     * return <b>false </b>.
+     *
+     * @param key the key to be checked
+     * @return a flag if this key is contained in this configuration
+     */
+    public boolean containsKey(String key)
+    {
+        return getProperty(key) != null;
+    }
+
+    /**
+     * Sets the value of the specified property.
+     *
+     * @param key the key of the property to set
+     * @param value the new value of this property
+     */
+    @Override
+    public void setProperty(String key, Object value)
+    {
+        fireEvent(EVENT_SET_PROPERTY, key, value, true);
+
+        // Update the existing nodes for this property
+        NodeList<T> nodes = fetchNodeList(key);
+        Iterator<?> itValues;
+        if (!isDelimiterParsingDisabled())
+        {
+            itValues = PropertyConverter.toIterator(value, getListDelimiter());
+        }
+        else
+        {
+            itValues = Collections.singleton(value).iterator();
+        }
+
+        int index = 0;
+        while (index < nodes.size() && itValues.hasNext())
+        {
+            nodes.setValue(index, itValues.next(), getNodeHandler());
+            index++;
+        }
+
+        // Add additional nodes if necessary
+        while (itValues.hasNext())
+        {
+            addPropertyDirect(key, itValues.next());
+        }
+
+        // Remove remaining nodes
+        while (index < nodes.size())
+        {
+            removeListElement(nodes, index++, true);
+        }
+
+        fireEvent(EVENT_SET_PROPERTY, key, value, false);
+    }
+
+    /**
+     * Removes all values of the property with the given name and of keys that
+     * start with this name. So if there is a property with the key
+     * &quot;foo&quot; and a property with the key &quot;foo.bar&quot;, a call
+     * of <code>clearTree("foo")</code> would remove both properties.
+     *
+     * @param key the key of the property to be removed
+     */
+    public void clearTree(String key)
+    {
+        removeNodeList(key, EVENT_CLEAR_TREE, false);
+    }
+
+    /**
+     * Removes the property with the given key. Properties with names that start
+     * with the given key (i.e. properties below the specified key in the
+     * hierarchy) won't be affected.
+     *
+     * @param key the key of the property to be removed
+     */
+    @Override
+    public void clearProperty(String key)
+    {
+        removeNodeList(key, EVENT_CLEAR_PROPERTY, true);
+    }
+
+    /**
+     * Removes the list element with the specified index from this
+     * configuration. This method calls the appropriate remove method depending
+     * on the type of the list element.
+     *
+     * @param nodes the node list
+     * @param index the index
+     * @param clear a flag whether the element should only be cleared or
+     *        completely removed
+     */
+    private void removeListElement(NodeList<T> nodes, int index, boolean clear)
+    {
+        if (nodes.isNode(index))
+        {
+            if (clear)
+            {
+                clearNode(nodes.getNode(index));
+            }
+            else
+            {
+                removeNode(nodes.getNode(index));
+            }
+        }
+        else
+        {
+            T parent = nodes.getAttributeParent(index);
+            getNodeHandler().removeAttribute(parent,
+                    nodes.getName(index, getNodeHandler()));
+            removeNodeIfUndefined(parent);
+        }
+    }
+
+    /**
+     * Removes or clears all nodes or attributes matched by the given key.
+     *
+     * @param key the key
+     * @param event the event to fire
+     * @param clear determines whether the elements are cleared or removed
+     */
+    private void removeNodeList(String key, int event, boolean clear)
+    {
+        fireEvent(event, key, null, true);
+        NodeList<T> nodes = fetchNodeList(key);
+
+        for (int index = 0; index < nodes.size(); index++)
+        {
+            removeListElement(nodes, index, clear);
+        }
+
+        fireEvent(event, key, null, false);
+    }
+
+    /**
+     * Returns an iterator with all keys defined in this configuration. Note
+     * that the keys returned by this method will not contain any indices. This
+     * means that some structure will be lost.</p>
+     *
+     * @return an iterator with the defined keys in this configuration
+     */
+    public Iterator<String> getKeys()
+    {
+        DefinedKeysVisitor visitor = new DefinedKeysVisitor();
+        visit(getConfigurationSource().getRootNode(), visitor);
+
+        return visitor.getKeyList().iterator();
+    }
+
+    /**
+     * Returns an iterator with all keys defined in this configuration that
+     * start with the given prefix. The returned keys will not contain any
+     * indices.
+     *
+     * @param prefix the prefix of the keys to start with
+     * @return an iterator with the found keys
+     */
+    @Override
+    public Iterator<String> getKeys(String prefix)
+    {
+        DefinedKeysVisitor visitor = new DefinedKeysVisitor(prefix);
+        if (containsKey(prefix))
+        {
+            // explicitly add the prefix
+            visitor.getKeyList().add(prefix);
+        }
+
+        NodeList<T> nodes = fetchNodeList(prefix);
+
+        for (int i = 0; i < nodes.size(); i++)
+        {
+            if (nodes.isNode(i))
+            {
+                for (T child : getNodeHandler().getChildren(nodes.getNode(i)))
+                {
+                    visit(child, visitor);
+                }
+                visitor.appendAttributes(nodes.getNode(i), prefix,
+                        getNodeHandler());
+            }
+        }
+
+        return visitor.getKeyList().iterator();
+    }
+
+    /**
+     * Returns the maximum defined index for the given key. This is useful if
+     * there are multiple values for this key. They can then be addressed
+     * separately by specifying indices from 0 to the return value of this
+     * method.
+     *
+     * @param key the key to be checked
+     * @return the maximum defined index for this key
+     */
+    public int getMaxIndex(String key)
+    {
+        NodeList<T> nodes = fetchNodeList(key);
+        int cnt = 0;
+
+        for (int index = 0; index < nodes.size(); index++)
+        {
+            Object value = nodes.getValue(index, getNodeHandler());
+            if (value instanceof Collection<?>)
+            {
+                // if there are multiple values, count them all
+                cnt += ((Collection<?>) value).size();
+            }
+            else
+            {
+                cnt++;
+            }
+        }
+
+        return cnt - 1;
+    }
+
+    /**
+     * Helper method for fetching a list of all nodes that are addressed by the
+     * specified key.
+     *
+     * @param key the key
+     * @return a list with all affected nodes (never <b>null </b>)
+     */
+    protected NodeList<T> fetchNodeList(String key)
+    {
+        return getExpressionEngine().query(
+                getConfigurationSource().getRootNode(), key, getNodeHandler());
+    }
+
+    /**
+     * Visits the specified configuration node. This method implements the
+     * traversal of the node hierarchy starting with the specified node.
+     *
+     * @param node the node to be visited
+     * @param visitor the visitor
+     */
+    protected void visit(T node, NodeVisitor<T> visitor)
+    {
+        if (!visitor.terminate())
+        {
+            visitor.visitBeforeChildren(node, getNodeHandler());
+
+            for (Iterator<T> it = getNodeHandler().getChildren(node).iterator(); it
+                    .hasNext()
+                    && !visitor.terminate();)
+            {
+                visit(it.next(), visitor);
+            }
+
+            visitor.visitAfterChildren(node, getNodeHandler());
+        }
+    }
+
+    /**
+     * Checks if the specified node is defined.
+     *
+     * @param node the node to be checked
+     * @return a flag if this node is defined
+     */
+    protected boolean nodeDefined(T node)
+    {
+        DefinedVisitor<T> visitor = new DefinedVisitor<T>();
+        visit(node, visitor);
+        return visitor.isDefined();
+    }
+
+    /**
+     * Removes the specified node from this configuration. This method ensures
+     * that parent nodes that become undefined by this operation are also
+     * removed.
+     *
+     * @param node the node to be removed
+     */
+    protected void removeNode(T node)
+    {
+        T parent = getNodeHandler().getParent(node);
+        if (parent != null)
+        {
+            getNodeHandler().removeChild(parent, node);
+            removeNodeIfUndefined(parent);
+        }
+    }
+
+    /**
+     * Clears the value of the specified node. If the node becomes undefined by
+     * this operation, it is removed from the hierarchy.
+     *
+     * @param node the node to be cleared
+     */
+    protected void clearNode(T node)
+    {
+        getNodeHandler().setValue(node, null);
+        removeNodeIfUndefined(node);
+    }
+
+    /**
+     * Removes the specified node if it is undefined.
+     *
+     * @param node the node
+     */
+    private void removeNodeIfUndefined(T node)
+    {
+        if (!nodeDefined(node))
+        {
+            removeNode(node);
+        }
+    }
+
+    /**
+     * Creates a new node object with the specified name. This base
+     * implementation delegates to the <code>NodeHandler</code> for creating a
+     * new node.
+     *
+     * @param parent the parent of the new node
+     * @param name the name of the new node
+     * @return the new node
+     */
+    protected T createNode(T parent, String name)
+    {
+        return getNodeHandler().addChild(parent, name);
+    }
+
+    /**
+     * Creates a new node object with the specified name and value. This base
+     * implementation delegates to the <code>NodeHandler</code> for creating a
+     * new node.
+     *
+     * @param parent the parent of the new node
+     * @param name the name of the new node
+     * @param value the value of the new node
+     * @return the new node
+     */
+    protected T createNode(T parent, String name, Object value)
+    {
+        return getNodeHandler().addChild(parent, name, value);
+    }
+
+    /**
+     * Helper method for processing a <code>NodeAddData</code> object obtained
+     * from the expression engine. This method will create all new nodes and set
+     * the value of the last node, which represents the newly added property.
+     *
+     * @param data the data object
+     * @param value the value of the new property
+     * @return the new node (<b>null</b> if an attribute was added)
+     */
+    protected T processNodeAddData(NodeAddData<T> data, Object value)
+    {
+        T node = data.getParent();
+
+        // Create missing nodes on the path
+        for (String nodeName : data.getPathNodes())
+        {
+            node = createNode(node, nodeName);
+        }
+
+        // Add the new property
+        return addNodeValue(node, data.getNewNodeName(), value, data
+                .isAttribute());
+    }
+
+    /**
+     * Adds a new value to a node, which can either be a child node or an
+     * attribute. This method is called by <code>processNodeAddData()</code> for
+     * the final node to be added. It can be overridden by concrete sub classes
+     * with specific requirements for adding values. This base implementation
+     * uses the <code>NodeHandler</code> of this configuration for either adding
+     * a new child node or an attribute value.
+     *
+     * @param parent the parent node (to which a value should be added)
+     * @param name the name of the property to be added
+     * @param value the value itself
+     * @param attr a flag whether a child node or an attribute should be added
+     * @return the newly created child node or <b>null</b> for an attribute
+     */
+    protected T addNodeValue(T parent, String name, Object value, boolean attr)
+    {
+        if (attr)
+        {
+            getNodeHandler().addAttributeValue(parent, name, value);
+            return null;
+        }
+        else
+        {
+            return createNode(parent, name, value);
+        }
+    }
+
+    // TODO A temporary override to make this method visible to SubConfiguration
+    @Override
+    protected Object interpolate(Object value)
+    {
+        return super.interpolate(value);
+    }
+
+    /**
+     * A specialized visitor that checks if a node is defined.
+     * &quot;Defined&quot; in this terms means that the node or at least one of
+     * its sub nodes is associated with a value.
+     */
+    private static class DefinedVisitor<T> extends NodeVisitorAdapter<T>
+    {
+        /** Stores the defined flag. */
+        private boolean defined;
+
+        /**
+         * Checks if iteration should be stopped. This can be done if the first
+         * defined node is found.
+         *
+         * @return a flag if iteration should be stopped
+         */
+        @Override
+        public boolean terminate()
+        {
+            return isDefined();
+        }
+
+        /**
+         * Visits the node. Checks if a value is defined.
+         *
+         * @param node the actual node
+         */
+        @Override
+        public void visitBeforeChildren(T node, NodeHandler<T> handler)
+        {
+            defined = handler.isDefined(node);
+        }
+
+        /**
+         * Returns the defined flag.
+         *
+         * @return the defined flag
+         */
+        public boolean isDefined()
+        {
+            return defined;
+        }
+    }
+
+    /**
+     * A specialized visitor that fills a list with keys that are defined in a
+     * node hierarchy.
+     */
+    private class DefinedKeysVisitor extends NodeVisitorAdapter<T>
+    {
+        /** Stores the list to be filled. */
+        private Set<String> keyList;
+
+        /** A stack with the keys of the already processed nodes. */
+        private Stack<String> parentKeys;
+
+        /**
+         * Default constructor.
+         */
+        public DefinedKeysVisitor()
+        {
+            keyList = new LinkedHashSet<String>();
+            parentKeys = new Stack<String>();
+        }
+
+        /**
+         * Creates a new <code>DefinedKeysVisitor</code> instance and sets the
+         * prefix for the keys to fetch.
+         *
+         * @param prefix the prefix
+         */
+        public DefinedKeysVisitor(String prefix)
+        {
+            this();
+            parentKeys.push(prefix);
+        }
+
+        /**
+         * Returns the list with all defined keys.
+         *
+         * @return the list with the defined keys
+         */
+        public Set<String> getKeyList()
+        {
+            return keyList;
+        }
+
+        /**
+         * Visits the node after its children has been processed. Removes this
+         * node's key from the stack.
+         *
+         * @param node the node
+         * @param handler the node handler
+         */
+        @Override
+        public void visitAfterChildren(T node, NodeHandler<T> handler)
+        {
+            parentKeys.pop();
+        }
+
+        /**
+         * Visits the specified node. If this node has a value, its key is added
+         * to the internal list.
+         *
+         * @param node the node to be visited
+         * @param handler the node handler
+         */
+        @Override
+        public void visitBeforeChildren(T node, NodeHandler<T> handler)
+        {
+            String parentKey = parentKeys.isEmpty() ? null : parentKeys.peek();
+            String key = getExpressionEngine()
+                    .nodeKey(node, parentKey, handler);
+            parentKeys.push(key);
+            if (handler.getValue(node) != null)
+            {
+                keyList.add(key);
+            }
+
+            appendAttributes(node, key, handler);
+        }
+
+        /**
+         * Adds the keys of the attributes of the given node to the internal key
+         * list.
+         *
+         * @param node the parent node
+         * @param parentKey the key of the parent node
+         * @param handler the node handler
+         */
+        public void appendAttributes(T node, String parentKey,
+                NodeHandler<T> handler)
+        {
+            List<String> attributes = handler.getAttributes(node);
+            for (String attr : attributes)
+            {
+                keyList.add(getExpressionEngine().attributeKey(node, parentKey,
+                        attr, handler));
+            }
+        }
+    }
+}

Propchange: commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/base/ConfigurationImpl.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/base/ConfigurationImpl.java
------------------------------------------------------------------------------
    svn:keywords = Date Author Id Revision HeadURL

Propchange: commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/base/ConfigurationImpl.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/base/SubConfiguration.java
URL: http://svn.apache.org/viewvc/commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/base/SubConfiguration.java?rev=803697&view=auto
==============================================================================
--- commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/base/SubConfiguration.java (added)
+++ commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/base/SubConfiguration.java Wed Aug 12 20:21:06 2009
@@ -0,0 +1,381 @@
+/*
+ * 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.configuration2.base;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.commons.configuration2.expr.NodeHandler;
+import org.apache.commons.configuration2.expr.NodeList;
+
+/**
+ * <p>
+ * A specialized hierarchical configuration class that wraps a single node of
+ * its parent configuration.
+ * </p>
+ * <p>
+ * Configurations of this type are initialized with a parent configuration and a
+ * configuration node of this configuration. This node becomes the root node of
+ * the sub configuration. All property accessor methods are evaluated
+ * relative to this root node. A good use case for a
+ * <code>SubConfiguration</code> is when multiple properties from a
+ * specific sub tree of the whole configuration need to be accessed. Then a
+ * <code>SubConfiguration</code> can be created with the parent node of
+ * the affected sub tree as root node. This allows for simpler property keys and
+ * is also more efficient.
+ * </p>
+ * <p>
+ * A sub configuration and its parent configuration operate on the same
+ * hierarchy of configuration nodes. So if modifications are performed at the
+ * sub configuration, these changes are immediately visible in the parent
+ * configuration. Analogously will updates of the parent configuration affect
+ * the sub configuration if the sub tree spanned by the sub
+ * configuration's root node is involved.
+ * </p>
+ * <p>
+ * There are however changes at the parent configuration, which cause the
+ * sub configuration to become detached. An example for such a change is a
+ * reload operation of a file-based configuration, which replaces all nodes of
+ * the parent configuration. The sub configuration per default still
+ * references the old nodes. Another example are list structures: a sub
+ * configuration can be created to point on the <em>i</em>th element of the
+ * list. Now list elements can be added or removed, so that the list elements'
+ * indices change. In such a scenario the sub configuration would always
+ * point to the same list element, regardless of its current index.
+ * </p>
+ * <p>
+ * To solve these problems and make a sub configuration aware of
+ * such structural changes of its parent, it is possible to associate a
+ * sub configuration with a configuration key. This can be done by calling
+ * the <code>setSubnodeKey()</code> method. If here a key is set, the sub
+ * configuration will evaluate it on each access, thus ensuring that it is
+ * always in sync with its parent. In this mode the sub configuration really
+ * behaves like a live-view on its parent. The price for this is a decreased
+ * performance because now an additional evaluation has to be performed on each
+ * property access. So this mode should only be used if necessary; if for
+ * instance a sub configuration is only used for a temporary convenient
+ * access to a complex configuration, there is no need to make it aware for
+ * structural changes of its parent. If a sub configuration is created
+ * using the <code>{@link ConfigurationImpl#configurationAt(String, boolean)
+ * configurationAt()}</code> method of <code>AbstractHierarchicalConfiguration</code>
+ * (which should be the preferred way), with an additional boolean parameter it
+ * can be specified whether the resulting sub configuration should be
+ * aware of structural changes or not. Then the configuration key will be
+ * automatically set.
+ * </p>
+ * <p>
+ * <em>Note:</em> At the moment support for creating a sub configuration
+ * that is aware of structural changes of its parent from another sub
+ * configuration (a "sub sub configuration") is limited. This only works if
+ * <ol><li>the sub configuration that serves as the parent for the new
+ * sub configuration is itself associated with a configuration key and</li>
+ * <li>the key passed in to create the new sub configuration is not too
+ * complex (if configuration keys are used that contain indices, a corresponding
+ * key that is valid from the parent configuration's point of view cannot be
+ * constructed).</li></ol>
+ * </p>
+ * <p>
+ * When a sub configuration is created, it inherits the settings of its
+ * parent configuration, e.g. some flags like the
+ * <code>throwExceptionOnMissing</code> flag or the settings for handling list
+ * delimiters) or the expression engine. If these settings are changed later in
+ * either the sub or the parent configuration, the changes are not visible
+ * for each other. So you could create a sub configuration and then change its
+ * expression engine without affecting the parent configuration.
+ * </p>
+ *
+ * @param <T> the type of configuration nodes used by this configuration
+ * @since 2.0
+ * @author Commons Configuration team
+ * @version $Id$
+ */
+public class SubConfiguration<T> extends ConfigurationImpl<T> implements HierarchicalConfigurationSource<T>
+{
+    /** Stores the parent configuration. */
+    private ConfigurationImpl<T> parent;
+
+    /** Stores the root node of this sub configuration.*/
+    private T rootNode;
+
+    /** Stores the key that was used to construct this configuration.*/
+    private String subnodeKey;
+
+    /**
+     * Creates a new instance of <code>SubnodeConfiguration</code> and
+     * initializes it with the parent configuration and the new root node.
+     *
+     * @param parent the parent configuration
+     * @param root the root node of this sub configuration
+     * @throws IllegalArgumentException if the parent or the root node are <b>null</b>
+     */
+    public SubConfiguration(ConfigurationImpl<T> parent, T root)
+    {
+        if (parent == null)
+        {
+            throw new IllegalArgumentException(
+                    "Parent configuration must not be null!");
+        }
+        if (root == null)
+        {
+            throw new IllegalArgumentException("Root node must not be null!");
+        }
+
+        rootNode = root;
+        this.parent = parent;
+        initFromParent(parent);
+    }
+
+    /**
+     * Returns the parent configuration of this sub configuration.
+     *
+     * @return the parent configuration
+     */
+    public ConfigurationImpl<T> getParent()
+    {
+        return parent;
+    }
+
+    /**
+     * Returns the key that was used to construct this configuration. If here a
+     * non-<b>null</b> value is returned, the sub configuration will
+     * always check its parent for structural changes and reconstruct itself if
+     * necessary.
+     *
+     * @return the key for selecting this configuration's root node
+     */
+    public String getSubnodeKey()
+    {
+        return subnodeKey;
+    }
+
+    /**
+     * Sets the key to the root node of this sub configuration. If here a
+     * key is set, the sub configuration will behave like a live-view on its
+     * parent for this key. See the class comment for more details.
+     *
+     * @param subnodeKey the key used to construct this configuration
+     */
+    public void setSubnodeKey(String subnodeKey)
+    {
+        this.subnodeKey = subnodeKey;
+    }
+
+    /**
+     * Returns the root node for this configuration. If a sub key is set,
+     * this implementation re-evaluates this key to find out if this sub
+     * configuration needs to be reconstructed. This ensures that the sub
+     * configuration is always synchronized with its parent configuration.
+     *
+     * @return the root node of this configuration
+     * @see #setSubnodeKey(String)
+     */
+    public T getRootNode()
+    {
+        if (getSubnodeKey() != null)
+        {
+            try
+            {
+                NodeList<T> nodes = getParent().fetchNodeList(getSubnodeKey());
+                if (nodes.size() != 1 || !nodes.isNode(0))
+                {
+                    // key is invalid, so detach this sub configuration
+                    setSubnodeKey(null);
+                }
+                else
+                {
+                    T currentRoot = nodes.getNode(0);
+                    if (currentRoot != rootNode)
+                    {
+                        // the root node was changed due to a change of the parent
+                        fireEvent(EVENT_SUBNODE_CHANGED, null, null, true);
+                        rootNode = currentRoot;
+                        fireEvent(EVENT_SUBNODE_CHANGED, null, null, false);
+                    }
+                    return currentRoot;
+                }
+            }
+            catch (Exception ex)
+            {
+                // Evaluation of the key caused an exception. Probably the
+                // expression engine has changed on the parent. Detach this
+                // configuration, there is not much we can do about this.
+                setSubnodeKey(null);
+            }
+        }
+
+        return rootNode; // use stored root node
+    }
+
+    /**
+     * Dummy implementation of this {@code HierarchicalConfigurationSource}
+     * method. Event listeners are not supported by this implementation.
+     * @param l the listener to be added
+     */
+    public void addConfigurationSourceListener(ConfigurationSourceListener l)
+    {
+        throw new UnsupportedOperationException("Not implemented!");
+    }
+
+    /**
+     * Dummy implementation of this {@code HierarchicalConfigurationSource}
+     * method. Event listeners are not supported by this implementation.
+     * @param l the listener to be removed
+     */
+    public boolean removeConfigurationSourceListener(
+            ConfigurationSourceListener l)
+    {
+        throw new UnsupportedOperationException("Not implemented!");
+    }
+
+    /**
+     * Dummy implementation of this {@code HierarchicalConfigurationSource}
+     * method. It is not supported to set a different root node.
+     * @param root the new root node
+     */
+    public void setRootNode(T root)
+    {
+        throw new UnsupportedOperationException("Not implemented!");
+    }
+
+    /**
+     * Returns the underlying {@code HierarchicalConfigurationSource}. This
+     * configuration acts as its own configuration source.
+     * @return the associated {@code HierarchicalConfigurationSource}
+     */
+    @Override
+    public HierarchicalConfigurationSource<T> getConfigurationSource()
+    {
+        return this;
+    }
+
+    /**
+     * Returns the {@code NodeHandler} for this configuration. This handler is
+     * obtained from the parent.
+     * @return the {@code NodeHandler} for this configuration
+     */
+    @Override
+    public NodeHandler<T> getNodeHandler()
+    {
+        return getParent().getNodeHandler();
+    }
+
+    /**
+     * Returns a hierarchical configuration object for the given sub node.
+     * This implementation will ensure that the returned
+     * <code>SubConfiguration</code> object will have the same parent as
+     * this object.
+     *
+     * @param node the sub node, for which the configuration is to be created
+     * @return a hierarchical configuration for this sub node
+     */
+    @Override
+    protected SubConfiguration<T> createSubnodeConfiguration(T node)
+    {
+        SubConfiguration<T> result = new SubConfiguration<T>(getParent(), node);
+        getParent().registerSubnodeConfiguration(result);
+        return result;
+    }
+
+    /**
+     * Returns a hierarchical configuration object for the given sub node that
+     * is aware of structural changes of its parent. Works like the method with
+     * the same name, but also sets the subnode key for the new sub
+     * configuration, so it can check whether the parent has been changed. This
+     * only works if this sub configuration has itself a valid sub key.
+     * So if a sub configuration that should be aware of structural changes
+     * is created from an already existing sub configuration, this sub
+     * configuration must also be aware of such changes.
+     *
+     * @param node the sub node, for which the configuration is to be created
+     * @param subnodeKey the construction key
+     * @return a hierarchical configuration for this sub node
+     */
+    @Override
+    protected SubConfiguration<T> createSubnodeConfiguration(
+            T node, String subnodeKey)
+    {
+        SubConfiguration<T> result = createSubnodeConfiguration(node);
+
+        if (getSubnodeKey() != null)
+        {
+            // construct the correct subnode key
+            // determine path to root node
+            List<T> lstPathToRoot = new ArrayList<T>();
+            T top = rootNode;
+            T nd = node;
+            while (nd != top)
+            {
+                lstPathToRoot.add(nd);
+                nd = getNodeHandler().getParent(nd);
+            }
+
+            // construct the keys for the nodes on this path
+            Collections.reverse(lstPathToRoot);
+            String key = getSubnodeKey();
+            for (T currentNode : lstPathToRoot)
+            {
+                key = getParent().getExpressionEngine().nodeKey(
+                        currentNode, key, getNodeHandler());
+            }
+            result.setSubnodeKey(key);
+        }
+
+        return result;
+    }
+
+    /**
+     * Creates a new node. This task is delegated to the parent.
+     *
+     * @param parent the parent node
+     * @param name the node's name
+     * @return the new node
+     */
+    @Override
+    protected T createNode(T parent, String name)
+    {
+        return getParent().createNode(parent, name);
+    }
+
+    /**
+     * Initializes this sub configuration from the given parent
+     * configuration. This method is called by the constructor. It will copy
+     * many settings from the parent.
+     *
+     * @param parentConfig the parent configuration
+     */
+    protected void initFromParent(ConfigurationImpl<T> parentConfig)
+    {
+        setExpressionEngine(parentConfig.getExpressionEngine());
+        setListDelimiter(parentConfig.getListDelimiter());
+        setDelimiterParsingDisabled(parentConfig.isDelimiterParsingDisabled());
+        setThrowExceptionOnMissing(parentConfig.isThrowExceptionOnMissing());
+    }
+
+    /**
+     * Performs interpolation. This implementation will ask the parent
+     * configuration to perform the interpolation so that variables can be
+     * evaluated in the global context.
+     *
+     * @param value the value to be interpolated
+     */
+    @Override
+    protected Object interpolate(Object value)
+    {
+        return getParent().interpolate(value);
+    }
+}

Propchange: commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/base/SubConfiguration.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/base/SubConfiguration.java
------------------------------------------------------------------------------
    svn:keywords = Date Author Id Revision HeadURL

Propchange: commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/base/SubConfiguration.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain