Author: oheger
Date: Sat Aug 15 20:13:06 2009
New Revision: 804523

URL: http://svn.apache.org/viewvc?rev=804523&view=rev
Log:
Added HierarchicalSourceAdapter class for transforming a plain 
ConfigurationSource into a hierarchical one.

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

Modified: 
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=804523&r1=804522&r2=804523&view=diff
==============================================================================
--- 
commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/base/ConfigurationImpl.java
 (original)
+++ 
commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/base/ConfigurationImpl.java
 Sat Aug 15 20:13:06 2009
@@ -58,8 +58,7 @@
  * </p>
  *
  * @author Commons Configuration team
- * @version $Id: AbstractHierarchicalConfiguration.java 786440 2009-06-19
- *          10:35:01Z ebourg $
+ * @version $Id$
  * @since 2.0
  * @param <T> the type of the nodes this configuration deals with
  */

Added: 
commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/base/HierarchicalSourceAdapter.java
URL: 
http://svn.apache.org/viewvc/commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/base/HierarchicalSourceAdapter.java?rev=804523&view=auto
==============================================================================
--- 
commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/base/HierarchicalSourceAdapter.java
 (added)
+++ 
commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/base/HierarchicalSourceAdapter.java
 Sat Aug 15 20:13:06 2009
@@ -0,0 +1,407 @@
+/*
+ * 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.Iterator;
+
+import org.apache.commons.configuration2.expr.NodeHandler;
+import org.apache.commons.configuration2.tree.ConfigurationNode;
+
+/**
+ * <p>
+ * An adapter implementation for converting a &quot;flat&quot;
+ * {...@link ConfigurationSource} into a hierarchical one.
+ * </p>
+ * <p>
+ * {...@link ConfigurationImpl}, the main implementation of the
+ * {...@link Configuration} interface, requires a hierarchical configuration 
source
+ * for accessing configuration settings. It does not work with sources
+ * implementing only the {...@link ConfigurationSource} interface out of the 
box.
+ * With this adapter class a {...@link ConfigurationSource} object can be 
treated
+ * as a {...@link HierarchicalConfigurationSource}.
+ * </p>
+ * <p>
+ * The idea behind this class is that it dynamically populates a
+ * {...@link ConfigurationImpl} object (living in memory) with the properties
+ * stored in the configuration source. This causes the data to be stored in a
+ * truly hierarchical structure enabling sophisticated queries. The in-memory
+ * configuration source of this configuration is then used to implement the
+ * methods of the {...@link HierarchicalConfigurationSource} interface.
+ * </p>
+ * <p>
+ * Changes at the data of the configuration used for the transformation are not
+ * written back into the original {...@link ConfigurationSource} automatically.
+ * This can be done by calling the {...@code writeBack()} method. This will 
clear
+ * the original source and then copy all data from this source into it.
+ * </p>
+ * <p>
+ * It is possible to configure this adapter to register as an event listener at
+ * the original {...@link ConfigurationSource}. Every change event fired by the
+ * {...@link ConfigurationSource} causes the configuration used internally to 
be
+ * re-constructed with the current data of the {...@link ConfigurationSource}. 
This
+ * will throw away all changes made at this hierarchical source! So this mode
+ * should only be used for providing a read-only hierarchical view for a plain
+ * {...@link ConfigurationSource}.
+ * </p>
+ *
+ * @author Commons Configuration team
+ * @version $Id$
+ */
+public class HierarchicalSourceAdapter implements
+        HierarchicalConfigurationSource<ConfigurationNode>,
+        ConfigurationSourceListener
+{
+    /**
+     * Stores the original source that is transformed by this adapter.
+     */
+    private final ConfigurationSource originalSource;
+
+    /** The configuration used internally for the transformation. */
+    private Configuration<ConfigurationNode> transformedConfig;
+
+    /**
+     * A flag whether the original configuration source should be monitored to
+     * react on changes.
+     */
+    private final boolean monitorChanges;
+
+    /**
+     * Creates a new instance of {...@code HierarchicalSourceAdapter} and
+     * initializes it with the {...@code ConfigurationSource} to wrap and the 
flag
+     * whether changes of this {...@code ConfigurationSource} should be 
monitored.
+     *
+     * @param wrappedSource the original {...@code ConfigurationSource} (must 
not
+     *        be <b>null</b>)
+     * @param monitorChanges the flag whether changes of the original source
+     *        should cause this source to update its data
+     * @throws IllegalArgumentException if the {...@code ConfigurationSource} 
is
+     *         <b>null</b>
+     */
+    public HierarchicalSourceAdapter(ConfigurationSource wrappedSource,
+            boolean monitorChanges)
+    {
+        if (wrappedSource == null)
+        {
+            throw new IllegalArgumentException(
+                    "Original ConfigurationSource must not be null!");
+        }
+
+        originalSource = wrappedSource;
+        this.monitorChanges = monitorChanges;
+
+        if (monitorChanges)
+        {
+            wrappedSource.addConfigurationSourceListener(this);
+        }
+    }
+
+    /**
+     * Creates a new instance of {...@code HierarchicalSourceAdapter} and
+     * initializes it with the {...@code ConfigurationSource} to wrap. Changes 
of
+     * the wrapped source are not monitored.
+     *
+     * @param wrappedSource the original {...@code ConfigurationSource} (must 
not
+     *        be <b>null</b>)
+     * @throws IllegalArgumentException if the {...@code ConfigurationSource} 
is
+     *         <b>null</b>
+     */
+    public HierarchicalSourceAdapter(ConfigurationSource wrappedSource)
+    {
+        this(wrappedSource, false);
+    }
+
+    /**
+     * An utility method for copying the content of the specified {...@code
+     * ConfigurationSource} into the given {...@code Configuration}. This class
+     * iterates over all properties stored in the {...@code 
ConfigurationSource}
+     * and adds their values to the {...@code Configuration}. Existing values 
in
+     * the {...@code Configuration} are not overridden, but new values are 
added.
+     *
+     * @param config the target {...@code Configuration} (must not be 
<b>null</b>)
+     * @param source the {...@code ConfigurationSource} to be copied (must not 
be
+     *        <b>null</b>)
+     * @throws IllegalArgumentException if a required parameter is <b>null</b>
+     */
+    public static void fillConfiguration(Configuration<?> config,
+            ConfigurationSource source)
+    {
+        if (config == null)
+        {
+            throw new IllegalArgumentException(
+                    "Configuration must not be null!");
+        }
+        if (source == null)
+        {
+            throw new IllegalArgumentException(
+                    "ConfigurationSource must not be null!");
+        }
+
+        doFillConfiguration(config, source);
+    }
+
+    /**
+     * An utility method for copying the content of the specified {...@code
+     * Configuration} into the given {...@code ConfigurationSource}. This 
method is
+     * the opposite of
+     * {...@link #fillConfiguration(Configuration, ConfigurationSource)}: It
+     * iterates over the keys in the {...@code Configuration} and adds their 
values
+     * to the {...@code ConfigurationSource}.
+     *
+     * @param source the target {...@code ConfigurationSource} (must not be
+     *        <b>null</b>)
+     * @param config the {...@code Configuration} to be copied (must not be
+     *        <b>null</b>)
+     * @throws IllegalArgumentException if a required parameter is <b>null</b>
+     */
+    public static void fillSource(ConfigurationSource source,
+            Configuration<?> config)
+    {
+        if (source == null)
+        {
+            throw new IllegalArgumentException(
+                    "ConfigurationSource must not be null!");
+        }
+        if (config == null)
+        {
+            throw new IllegalArgumentException(
+                    "Configuration must not be null!");
+        }
+
+        doFillSource(source, config);
+    }
+
+    /**
+     * Returns the original {...@code ConfigurationSource} that is wrapped by 
this
+     * adapter.
+     *
+     * @return the original {...@code ConfigurationSource}
+     */
+    public ConfigurationSource getOriginalSource()
+    {
+        return originalSource;
+    }
+
+    /**
+     * Returns a flag whether changes of the original {...@code
+     * ConfigurationSource} are monitored. A value of <b>true</b> means that
+     * this adapter is registered as a change listener at the configuration
+     * time. Every time a change event is received the data of this {...@code
+     * HierarchicalConfigurationSource} is invalidated, so it has to be
+     * re-constructed on next access.
+     *
+     * @return a flag whether changes of the wrapped source are monitored
+     */
+    public boolean isMonitorChanges()
+    {
+        return monitorChanges;
+    }
+
+    /**
+     * Writes the data stored in this {...@code 
HierarchicalConfigurationSource}
+     * into the original {...@code ConfigurationSource}. This method can be 
called
+     * to apply changes made at this source to the original source. Note that
+     * the original {...@code ConfigurationSource} may not be capable to fully 
deal
+     * with the hierarchical structure of the data stored in this {...@code
+     * HierarchicalConfigurationSource}. So some structure might be lost during
+     * this conversion.
+     */
+    public void writeBack()
+    {
+        if (isMonitorChanges())
+        {
+            // first de-register, so we do not receive our own updates
+            getOriginalSource().removeConfigurationSourceListener(this);
+        }
+
+        try
+        {
+            getOriginalSource().clear();
+            doFillSource(getOriginalSource(), getTransformedConfiguration());
+        }
+        finally
+        {
+            if (isMonitorChanges())
+            {
+                getOriginalSource().addConfigurationSourceListener(this);
+            }
+        }
+    }
+
+    /**
+     * Adds a {...@code ConfigurationSourceListener} to this source. This
+     * implementation delegates to the transformed source.
+     *
+     * @param l the listener to add
+     */
+    public void addConfigurationSourceListener(ConfigurationSourceListener l)
+    {
+        getTransformedSource().addConfigurationSourceListener(l);
+    }
+
+    /**
+     * Removes all data from this source. This implementation delegates to the
+     * transformed source.
+     */
+    public void clear()
+    {
+        getTransformedSource().clear();
+    }
+
+    /**
+     * Returns the {...@code NodeHandler} of this source. This implementation
+     * delegates to the transformed source.
+     *
+     * @return the {...@code NodeHandler} of this source
+     */
+    public NodeHandler<ConfigurationNode> getNodeHandler()
+    {
+        return getTransformedSource().getNodeHandler();
+    }
+
+    /**
+     * Returns the root node of this source. This implementation delegates to
+     * the transformed source.
+     *
+     * @return the root node of this source
+     */
+    public ConfigurationNode getRootNode()
+    {
+        return getTransformedSource().getRootNode();
+    }
+
+    /**
+     * Removes the specified {...@code ConfigurationSourceListener} from this
+     * source. This implementation delegates to the transformed source.
+     *
+     * @param l the listener to remove
+     * @return a flag whether the listener could be removed
+     */
+    public boolean removeConfigurationSourceListener(
+            ConfigurationSourceListener l)
+    {
+        return getTransformedSource().removeConfigurationSourceListener(l);
+    }
+
+    /**
+     * Sets the root node of this source. This implementation delegates to the
+     * transformed source.
+     *
+     * @param root the new root node
+     */
+    public void setRootNode(ConfigurationNode root)
+    {
+        getTransformedSource().setRootNode(root);
+    }
+
+    /**
+     * Notifies this object about a change of a monitored {...@code
+     * ConfigurationSource}. If this {...@code HierarchicalSourceAdapter} was
+     * constructed with the {...@code monitorChanges} flag set to <b>true</b>, 
it
+     * has registered itself as a change listener at the original {...@code
+     * ConfigurationSource}. Thus it receives change notifications whenever the
+     * original {...@code ConfigurationSource} is manipulated. This 
implementation
+     * just calls {...@link #invalidate()} which indicates that the data of 
this
+     * source has to be re-constructed.
+     *
+     * @param event the change event
+     */
+    public void configurationSourceChanged(ConfigurationSourceEvent event)
+    {
+        invalidate();
+    }
+
+    /**
+     * Returns a {...@code Configuration} that is used internally for the
+     * transformation of the original source to a hierarchical one. This
+     * configuration is populated with the data of the original source. 
Whenever
+     * a change in the original source is detected, this configuration is
+     * invalidated so that it has to be re-constructed.
+     *
+     * @return the {...@code Configuration} used internally for the 
transformation
+     */
+    protected synchronized Configuration<ConfigurationNode> 
getTransformedConfiguration()
+    {
+        if (transformedConfig == null)
+        {
+            // need to re-construct the configuration
+            transformedConfig = new ConfigurationImpl<ConfigurationNode>(
+                    new InMemoryConfigurationSource());
+            doFillConfiguration(transformedConfig, getOriginalSource());
+        }
+
+        return transformedConfig;
+    }
+
+    /**
+     * Returns the <em>transformed source</em>. This is a true hierarchical
+     * configuration source to which all operations on this adapter are
+     * delegated. This implementation uses an in-memory configuration source
+     * that is associated with a configuration to be populated and manipulated.
+     *
+     * @return the transformed {...@code HierarchicalConfigurationSource}
+     */
+    protected HierarchicalConfigurationSource<ConfigurationNode> 
getTransformedSource()
+    {
+        return getTransformedConfiguration().getConfigurationSource();
+    }
+
+    /**
+     * Invalidates the data in the transformed source. Calling this method
+     * causes the configuration used internally for the transformation to be
+     * reseted, so it has to be re-constructed when the next data access
+     * happens. It can be called, for instance, if a change of the original
+     * source is detected.
+     */
+    protected synchronized void invalidate()
+    {
+        transformedConfig = null;
+    }
+
+    /**
+     * Helper method for copying the data of a configuration source into a
+     * configuration.
+     *
+     * @param config the configuration to be filled
+     * @param source the configuration source
+     */
+    private static void doFillConfiguration(Configuration<?> config,
+            ConfigurationSource source)
+    {
+        for (Iterator<String> it = source.getKeys(); it.hasNext();)
+        {
+            String key = it.next();
+            config.addProperty(key, source.getProperty(key));
+        }
+    }
+
+    /**
+     * Helper method for copying the data of a configuration into a
+     * configuration source.
+     *
+     * @param source the configuration source
+     * @param config the configuration
+     */
+    private static void doFillSource(ConfigurationSource source,
+            Configuration<?> config)
+    {
+        for (Iterator<String> it = config.getKeys(); it.hasNext();)
+        {
+            String key = it.next();
+            source.addProperty(key, config.getProperty(key));
+        }
+    }
+}

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

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

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

Added: 
commons/proper/configuration/branches/configuration2_experimental/src/test/java/org/apache/commons/configuration2/base/TestHierarchicalSourceAdapter.java
URL: 
http://svn.apache.org/viewvc/commons/proper/configuration/branches/configuration2_experimental/src/test/java/org/apache/commons/configuration2/base/TestHierarchicalSourceAdapter.java?rev=804523&view=auto
==============================================================================
--- 
commons/proper/configuration/branches/configuration2_experimental/src/test/java/org/apache/commons/configuration2/base/TestHierarchicalSourceAdapter.java
 (added)
+++ 
commons/proper/configuration/branches/configuration2_experimental/src/test/java/org/apache/commons/configuration2/base/TestHierarchicalSourceAdapter.java
 Sat Aug 15 20:13:06 2009
@@ -0,0 +1,580 @@
+/*
+ * 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.Arrays;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+import junit.framework.TestCase;
+
+import org.apache.commons.configuration2.expr.ConfigurationNodeHandler;
+import org.apache.commons.configuration2.expr.NodeHandler;
+import org.apache.commons.configuration2.tree.ConfigurationNode;
+import org.apache.commons.lang.mutable.MutableObject;
+import org.easymock.EasyMock;
+import org.easymock.IAnswer;
+
+/**
+ * Test class for {...@code HierarchicalSourceAdapter}.
+ *
+ * @author Commons Configuration team
+ * @version $Id$
+ */
+public class TestHierarchicalSourceAdapter extends TestCase
+{
+    /** An array with the names of the test properties. */
+    private static final String[] KEYS = {
+            "db.user", "db.pwd", "db.driver", "gui.color.fg", "gui.color.bg",
+            "gui.width", "gui.height", "test"
+    };
+
+    /** An array with the values of the test properties. */
+    private static final Object[] VALUES = {
+            "scott", "elephant", "test.driver", "pink", "black", 320, 200, true
+    };
+
+    /** Constant for the key of a new property. */
+    private static final String NEW_KEY = "test2";
+
+    /** Constant for the value of the new property. */
+    private static final Object NEW_VALUE = Boolean.TRUE;
+
+    /**
+     * Helper method for creating a mock for a hierarchical source.
+     *
+     * @return the mock
+     */
+    private static HierarchicalConfigurationSource<ConfigurationNode> 
createHierarchicalSourceMock()
+    {
+        @SuppressWarnings("unchecked")
+        HierarchicalConfigurationSource<ConfigurationNode> mock = EasyMock
+                .createMock(HierarchicalConfigurationSource.class);
+        return mock;
+    }
+
+    /**
+     * Helper method for creating a mock for a configuration.
+     *
+     * @return the mock
+     */
+    private static Configuration<ConfigurationNode> createConfigurationMock()
+    {
+        @SuppressWarnings("unchecked")
+        Configuration<ConfigurationNode> mock = EasyMock
+                .createMock(Configuration.class);
+        return mock;
+    }
+
+    /**
+     * Prepares a mock source object to be queried for its properties for a
+     * transformation. This method prepares the mock to expect a number of
+     * getProperty() calls for all test properties.
+     *
+     * @param src the mock for the source
+     */
+    private static void prepareTransformPropertyValues(ConfigurationSource src)
+    {
+        for (int i = 0; i < KEYS.length; i++)
+        {
+            EasyMock.expect(src.getProperty(KEYS[i])).andReturn(VALUES[i]);
+        }
+    }
+
+    /**
+     * Prepares a mock source object for a writeBack() operation. The mock is
+     * initialized to expect addProperty() for all test properties and the new
+     * property.
+     *
+     * @param src the mock for the source
+     * @param clear a flag whether the clear() call should be expected, too
+     */
+    private static void prepareWriteBack(ConfigurationSource src, boolean 
clear)
+    {
+        if (clear)
+        {
+            src.clear();
+        }
+        for (int i = 0; i < KEYS.length; i++)
+        {
+            src.addProperty(KEYS[i], VALUES[i]);
+        }
+        src.addProperty(NEW_KEY, NEW_VALUE);
+    }
+
+    /**
+     * Tries to create an instance without a wrapped source. This should cause
+     * an exception.
+     */
+    public void testInitNoWrappedSource()
+    {
+        try
+        {
+            new HierarchicalSourceAdapter(null);
+            fail("Could create instance with null wrapped source!");
+        }
+        catch (IllegalArgumentException iex)
+        {
+            // ok
+        }
+    }
+
+    /**
+     * Tests the constructor that takes only the wrapped source.
+     */
+    public void testInit()
+    {
+        ConfigurationSource src = EasyMock
+                .createMock(ConfigurationSource.class);
+        EasyMock.replay(src);
+        HierarchicalSourceAdapter adapter = new HierarchicalSourceAdapter(src);
+        assertEquals("Wrong wrapped source", src, adapter.getOriginalSource());
+        assertFalse("Wrong monitor flag", adapter.isMonitorChanges());
+        EasyMock.verify(src);
+    }
+
+    /**
+     * Tests the constructor that enables monitoring changes.
+     */
+    public void testInitMonitor()
+    {
+        ConfigurationSource src = EasyMock
+                .createMock(ConfigurationSource.class);
+        EasyMock.replay(src);
+        final MutableObject listener = new MutableObject();
+        ConfigurationSource orgSrc = new ConfigurationSourceEventWrapper(src)
+        {
+            @Override
+            public void addConfigurationSourceListener(
+                    ConfigurationSourceListener l)
+            {
+                listener.setValue(l);
+            }
+        };
+        HierarchicalSourceAdapter adapter = new HierarchicalSourceAdapter(
+                orgSrc, true);
+        assertTrue("Wrong monitor flag", adapter.isMonitorChanges());
+        assertEquals("Listener not registered", adapter, listener.getValue());
+    }
+
+    /**
+     * Tests the clear() implementation.
+     */
+    public void testClear()
+    {
+        HierarchicalSourceAdapterTestImpl adapter = new 
HierarchicalSourceAdapterTestImpl();
+        adapter.mockSource = createHierarchicalSourceMock();
+        adapter.mockSource.clear();
+        EasyMock.replay(adapter.mockSource, adapter.getOriginalSource());
+        adapter.clear();
+        EasyMock.verify(adapter.mockSource, adapter.getOriginalSource());
+    }
+
+    /**
+     * Tests whether configuration source listener can be registered at the
+     * transformed source.
+     */
+    public void testAddConfigurationSourceListener()
+    {
+        ConfigurationSourceListener l = EasyMock
+                .createMock(ConfigurationSourceListener.class);
+        HierarchicalConfigurationSource<ConfigurationNode> src = 
createHierarchicalSourceMock();
+        HierarchicalSourceAdapterTestImpl adapter = new 
HierarchicalSourceAdapterTestImpl();
+        src.addConfigurationSourceListener(l);
+        EasyMock.replay(l, src);
+        adapter.mockSource = src;
+        adapter.addConfigurationSourceListener(l);
+        EasyMock.verify(l, src);
+    }
+
+    /**
+     * Tests whether a configuration source listener can be removed from the
+     * transformed source.
+     */
+    public void testRemoveConfigurationSourceListener()
+    {
+        ConfigurationSourceListener l = EasyMock
+                .createMock(ConfigurationSourceListener.class);
+        HierarchicalConfigurationSource<ConfigurationNode> src = 
createHierarchicalSourceMock();
+        HierarchicalSourceAdapterTestImpl adapter = new 
HierarchicalSourceAdapterTestImpl();
+        EasyMock.expect(src.removeConfigurationSourceListener(l)).andReturn(
+                Boolean.TRUE);
+        EasyMock.replay(l, src);
+        adapter.mockSource = src;
+        assertTrue("Wrong result", 
adapter.removeConfigurationSourceListener(l));
+        EasyMock.verify(l, src);
+    }
+
+    /**
+     * Tests whether the correct root node is returned.
+     */
+    public void testGetRootNode()
+    {
+        HierarchicalConfigurationSource<ConfigurationNode> src = 
createHierarchicalSourceMock();
+        HierarchicalSourceAdapterTestImpl adapter = new 
HierarchicalSourceAdapterTestImpl();
+        ConfigurationNode root = EasyMock.createMock(ConfigurationNode.class);
+        EasyMock.expect(src.getRootNode()).andReturn(root);
+        EasyMock.replay(src, root, adapter.getOriginalSource());
+        adapter.mockSource = src;
+        assertEquals("Wrong root node", root, adapter.getRootNode());
+        EasyMock.verify(src, root, adapter.getOriginalSource());
+    }
+
+    /**
+     * Tests whether a new root node can be set.
+     */
+    public void testSetRootNode()
+    {
+        HierarchicalConfigurationSource<ConfigurationNode> src = 
createHierarchicalSourceMock();
+        HierarchicalSourceAdapterTestImpl adapter = new 
HierarchicalSourceAdapterTestImpl();
+        ConfigurationNode root = EasyMock.createMock(ConfigurationNode.class);
+        src.setRootNode(root);
+        EasyMock.replay(src, root, adapter.getOriginalSource());
+        adapter.mockSource = src;
+        adapter.setRootNode(root);
+        EasyMock.verify(src, root, adapter.getOriginalSource());
+    }
+
+    /**
+     * Tests whether the correct node handler is returned.
+     */
+    public void testGetNodeHandler()
+    {
+        HierarchicalConfigurationSource<ConfigurationNode> src = 
createHierarchicalSourceMock();
+        HierarchicalSourceAdapterTestImpl adapter = new 
HierarchicalSourceAdapterTestImpl();
+        NodeHandler<ConfigurationNode> handler = new 
ConfigurationNodeHandler();
+        EasyMock.expect(src.getNodeHandler()).andReturn(handler);
+        EasyMock.replay(src, adapter.getOriginalSource());
+        adapter.mockSource = src;
+        assertEquals("Wrong node handler", handler, adapter.getNodeHandler());
+        EasyMock.verify(src, adapter.getOriginalSource());
+    }
+
+    /**
+     * Tests whether the given configuration contains the expected data.
+     *
+     * @param config the configuration to check
+     */
+    private void checkTransformedConfiguration(
+            Configuration<ConfigurationNode> config)
+    {
+        Set<String> keySet = new HashSet<String>(Arrays.asList(KEYS));
+        for (Iterator<String> it = config.getKeys(); it.hasNext();)
+        {
+            String key = it.next();
+            assertTrue("Unexpected key: " + key, keySet.remove(key));
+        }
+        assertTrue("Remaining keys: " + keySet, keySet.isEmpty());
+        for (int i = 0; i < KEYS.length; i++)
+        {
+            assertEquals("Wrong value for " + KEYS[i], VALUES[i], config
+                    .getProperty(KEYS[i]));
+        }
+        Configuration<ConfigurationNode> sub = config.configurationAt("gui");
+        assertEquals("Wrong sub property 1", 320, sub.getInt("width"));
+        assertEquals("Wrong sub property 2", "black", 
sub.getString("color.bg"));
+    }
+
+    /**
+     * Tests whether the plain configuration source is transformed into a
+     * hierarchical one.
+     */
+    public void testTransformation()
+    {
+        HierarchicalSourceAdapterTestImpl adapter = new 
HierarchicalSourceAdapterTestImpl();
+        prepareTransformPropertyValues(adapter.getOriginalSource());
+        EasyMock.expect(adapter.getOriginalSource().getKeys()).andReturn(
+                Arrays.asList(KEYS).iterator());
+        EasyMock.replay(adapter.getOriginalSource());
+        Configuration<ConfigurationNode> config = new 
ConfigurationImpl<ConfigurationNode>(
+                adapter);
+        checkTransformedConfiguration(config);
+        EasyMock.verify(adapter.getOriginalSource());
+    }
+
+    /**
+     * Tests whether changes of the original source are monitored and whether
+     * the transformed source is re-constructed if a change is noticed.
+     */
+    public void testTransformationMonitor()
+    {
+        ConfigurationSource source = EasyMock
+                .createMock(ConfigurationSource.class);
+        source
+                .addConfigurationSourceListener((ConfigurationSourceListener) 
EasyMock
+                        .anyObject());
+        prepareTransformPropertyValues(source);
+        EasyMock.expect(source.getKeys()).andReturn(
+                Arrays.asList(KEYS).iterator());
+        prepareTransformPropertyValues(source);
+        EasyMock.expect(source.getProperty(NEW_KEY)).andReturn(NEW_VALUE);
+        List<String> keyList = new ArrayList<String>(Arrays.asList(KEYS));
+        keyList.add(NEW_KEY);
+        EasyMock.expect(source.getKeys()).andReturn(keyList.iterator());
+        EasyMock.replay(source);
+        HierarchicalSourceAdapter adapter = new HierarchicalSourceAdapter(
+                source, true);
+        Configuration<ConfigurationNode> config = new 
ConfigurationImpl<ConfigurationNode>(
+                adapter);
+        assertFalse("New key already found", config.containsKey(NEW_KEY));
+        // simulate a change event
+        adapter.configurationSourceChanged(new ConfigurationSourceEvent(source,
+                ConfigurationSourceEvent.Type.ADD_PROPERTY, NEW_KEY, NEW_VALUE,
+                null, true));
+        adapter.configurationSourceChanged(new ConfigurationSourceEvent(source,
+                ConfigurationSourceEvent.Type.ADD_PROPERTY, NEW_KEY, NEW_VALUE,
+                null, false));
+        assertEquals("Wrong value of new property", NEW_VALUE, config
+                .getProperty(NEW_KEY));
+        assertEquals("Wrong value of old property", 320, config
+                .getInt("gui.width"));
+        EasyMock.verify(source);
+    }
+
+    /**
+     * Tests whether data stored in the adapter can be written back into the
+     * original source.
+     */
+    public void testWriteBack()
+    {
+        HierarchicalSourceAdapterTestImpl adapter = new 
HierarchicalSourceAdapterTestImpl();
+        EasyMock.expect(adapter.getOriginalSource().getKeys()).andReturn(
+                Arrays.asList(KEYS).iterator());
+        prepareTransformPropertyValues(adapter.getOriginalSource());
+        prepareWriteBack(adapter.getOriginalSource(), true);
+        EasyMock.replay(adapter.getOriginalSource());
+        Configuration<ConfigurationNode> config = new 
ConfigurationImpl<ConfigurationNode>(
+                adapter);
+        assertFalse("Configuration is empty", config.isEmpty());
+        config.addProperty(NEW_KEY, NEW_VALUE);
+        adapter.writeBack();
+        EasyMock.verify(adapter.getOriginalSource());
+    }
+
+    /**
+     * Tests the writeBack() method if monitoring of the original source is
+     * enabled. In this case the adapter should de-register itself before it
+     * changes the original source.
+     */
+    public void testWriteBackMonitor()
+    {
+        ConfigurationSource source = EasyMock
+                .createMock(ConfigurationSource.class);
+        final MutableObject expectedListener = new MutableObject();
+        source
+                .addConfigurationSourceListener((ConfigurationSourceListener) 
EasyMock
+                        .anyObject());
+        EasyMock.expect(source.getKeys()).andReturn(
+                Arrays.asList(KEYS).iterator());
+        prepareTransformPropertyValues(source);
+        EasyMock
+                .expect(
+                        source
+                                
.removeConfigurationSourceListener((ConfigurationSourceListener) EasyMock
+                                        .anyObject())).andAnswer(
+                        new IAnswer<Boolean>()
+                        {
+                            /* Check whether the expected listener is passed 
in.*/
+                            public Boolean answer() throws Throwable
+                            {
+                                assertEquals("Wrong event listener to remove",
+                                        expectedListener.getValue(), EasyMock
+                                                .getCurrentArguments()[0]);
+                                return Boolean.TRUE;
+                            }
+                        });
+        prepareWriteBack(source, true);
+        source
+                .addConfigurationSourceListener((ConfigurationSourceListener) 
EasyMock
+                        .anyObject());
+        EasyMock.expectLastCall().andAnswer(new IAnswer<Object>()
+        {
+            /* Check whether the expected listener is passed in.*/
+            public Object answer() throws Throwable
+            {
+                assertEquals("Wrong event listener to add", expectedListener
+                        .getValue(), EasyMock.getCurrentArguments()[0]);
+                return null;
+            }
+        });
+        EasyMock.replay(source);
+        HierarchicalSourceAdapterTestImpl adapter = new 
HierarchicalSourceAdapterTestImpl(
+                source, true);
+        expectedListener.setValue(adapter);
+        Configuration<ConfigurationNode> config = new 
ConfigurationImpl<ConfigurationNode>(
+                adapter);
+        assertFalse("Configuration is empty", config.isEmpty());
+        config.addProperty(NEW_KEY, NEW_VALUE);
+        adapter.writeBack();
+        EasyMock.verify(source);
+    }
+
+    /**
+     * Tests the utility method for copying a source into a configuration.
+     */
+    public void testFillConfiguration()
+    {
+        ConfigurationSource source = EasyMock
+                .createMock(ConfigurationSource.class);
+        EasyMock.expect(source.getKeys()).andReturn(
+                Arrays.asList(KEYS).iterator());
+        prepareTransformPropertyValues(source);
+        EasyMock.replay(source);
+        Configuration<ConfigurationNode> config = new 
ConfigurationImpl<ConfigurationNode>(
+                new InMemoryConfigurationSource());
+        HierarchicalSourceAdapter.fillConfiguration(config, source);
+        checkTransformedConfiguration(config);
+        EasyMock.verify(source);
+    }
+
+    /**
+     * Tries to call fillConfiguration() with a null configuration. This should
+     * cause an exception.
+     */
+    public void testFillConfigurationNullConfig()
+    {
+        ConfigurationSource source = EasyMock
+                .createMock(ConfigurationSource.class);
+        EasyMock.replay(source);
+        try
+        {
+            HierarchicalSourceAdapter.fillConfiguration(null, source);
+            fail("Null configuration not detected!");
+        }
+        catch (IllegalArgumentException iex)
+        {
+            EasyMock.verify(source);
+        }
+    }
+
+    /**
+     * Tries to call fillConfiguration() with a null source. This should cause
+     * an exception.
+     */
+    public void testFillConfigurationNullSource()
+    {
+        Configuration<ConfigurationNode> config = createConfigurationMock();
+        EasyMock.replay(config);
+        try
+        {
+            HierarchicalSourceAdapter.fillConfiguration(config, null);
+            fail("Null source not detected!");
+        }
+        catch (IllegalArgumentException iex)
+        {
+            EasyMock.verify(config);
+        }
+    }
+
+    /**
+     * Tests the utility method for copying a configuration into a source.
+     */
+    public void testFillSource()
+    {
+        ConfigurationSource source = EasyMock
+                .createMock(ConfigurationSource.class);
+        prepareWriteBack(source, false);
+        EasyMock.replay(source);
+        Configuration<ConfigurationNode> config = new 
ConfigurationImpl<ConfigurationNode>(
+                new InMemoryConfigurationSource());
+        for (int i = 0; i < KEYS.length; i++)
+        {
+            config.addProperty(KEYS[i], VALUES[i]);
+        }
+        config.addProperty(NEW_KEY, NEW_VALUE);
+        HierarchicalSourceAdapter.fillSource(source, config);
+        EasyMock.verify(source);
+    }
+
+    /**
+     * Tries to call fillSource() with a null configuration. This should cause
+     * an exception.
+     */
+    public void testFillSourceNullConfig()
+    {
+        ConfigurationSource source = EasyMock
+                .createMock(ConfigurationSource.class);
+        EasyMock.replay(source);
+        try
+        {
+            HierarchicalSourceAdapter.fillSource(source, null);
+            fail("Null configuration not detected!");
+        }
+        catch (IllegalArgumentException iex)
+        {
+            EasyMock.verify(source);
+        }
+    }
+
+    /**
+     * Tries to call fillSource() with a null source. This should cause an
+     * exception.
+     */
+    public void testFillSourceNullSource()
+    {
+        Configuration<ConfigurationNode> config = createConfigurationMock();
+        EasyMock.replay(config);
+        try
+        {
+            HierarchicalSourceAdapter.fillSource(null, config);
+            fail("Null source not detected!");
+        }
+        catch (IllegalArgumentException iex)
+        {
+            EasyMock.verify(config);
+        }
+    }
+
+    /**
+     * A specialized implementation of {...@code HierarchicalSourceAdapter} 
that
+     * overrides some methods to inject mock objects.
+     */
+    private static class HierarchicalSourceAdapterTestImpl extends
+            HierarchicalSourceAdapter
+    {
+        /** The mock transformed source. */
+        HierarchicalConfigurationSource<ConfigurationNode> mockSource;
+
+        public HierarchicalSourceAdapterTestImpl(
+                ConfigurationSource wrappedSource, boolean monitorChanges)
+        {
+            super(wrappedSource, monitorChanges);
+        }
+
+        /**
+         * Creates a new instance of {...@code 
HierarchicalSourceAdapterTestImpl}
+         * and sets a default mock object for the wrapped source.
+         */
+        public HierarchicalSourceAdapterTestImpl()
+        {
+            super(EasyMock.createMock(ConfigurationSource.class));
+        }
+
+        /**
+         * Either returns the mock source or calls the super method.
+         */
+        @Override
+        protected HierarchicalConfigurationSource<ConfigurationNode> 
getTransformedSource()
+        {
+            return (mockSource != null) ? mockSource : super
+                    .getTransformedSource();
+        }
+    }
+}

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

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

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


Reply via email to