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/24 20:35:54 UTC

svn commit: r807333 - in /commons/proper/configuration/branches/configuration2_experimental/src: main/java/org/apache/commons/configuration2/base/FlatNodeSourceAdapter.java test/java/org/apache/commons/configuration2/base/TestFlatNodeSourceAdapter.java

Author: oheger
Date: Mon Aug 24 18:35:51 2009
New Revision: 807333

URL: http://svn.apache.org/viewvc?rev=807333&view=rev
Log:
Added FlatNodeSourceAdapter class for transforming a plain ConfigurationSource into a hierarchy of FlatNode objects.

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

Added: commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/base/FlatNodeSourceAdapter.java
URL: http://svn.apache.org/viewvc/commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/base/FlatNodeSourceAdapter.java?rev=807333&view=auto
==============================================================================
--- commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/base/FlatNodeSourceAdapter.java (added)
+++ commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/base/FlatNodeSourceAdapter.java Mon Aug 24 18:35:51 2009
@@ -0,0 +1,224 @@
+/*
+ * 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;
+
+/**
+ * <p>
+ * An adapter implementation for converting a &quot;flat&quot;
+ * {@link ConfigurationSource} into a hierarchical one based on {@link FlatNode}
+ * node objects.
+ * </p>
+ * <p>
+ * An instance of this class is initialized with a reference to a {@code
+ * ConfigurationSource} object. All the data of this source is extracted and
+ * transformed into a hierarchical structure of {@link FlatNode} objects. On
+ * this node structure the operations defined by the {@code
+ * HierarchicalConfigurationSource} interface are implemented.
+ * </p>
+ * <p>
+ * The way the flat nodes are implemented, all manipulations on the node
+ * structure are directly reflected by the {@code ConfigurationSource}. The
+ * other direction works, too: This adapter registers itself as {@code
+ * ConfigurationSourceListener} at the wrapped {@code ConfigurationSource} (this
+ * implies that the wrapped source supports event notifications). Whenever a
+ * change event is received the node structure is invalidated, so it has to be
+ * re-created at next access. This ensures that the content of the wrapped
+ * {@code ConfigurationSource} and the flat node structure are always in sync.
+ * </p>
+ *
+ * @author Commons Configuration team
+ * @version $Id$
+ */
+public class FlatNodeSourceAdapter implements
+        HierarchicalConfigurationSource<FlatNode>, ConfigurationSourceListener
+{
+    /** The wrapped configuration source. */
+    private final ConfigurationSource originalSource;
+
+    /** The node handler for accessing the flat nodes. */
+    private final FlatNodeHandler nodeHandler;
+
+    /** The root node of the hierarchy of flat nodes. */
+    private FlatNode root;
+
+    /**
+     * Creates a new instance of {@code FlatNodeSourceAdapter} and initializes
+     * it with the {@code ConfigurationSource} to be transformed into a
+     * hierarchical one.
+     *
+     * @param wrappedSource the wrapped {@code ConfigurationSource} (must not be
+     *        <b>null</b>)
+     * @throws IllegalArgumentException if the passed in {@code
+     *         ConfigurationSource} is <b>null</b>
+     */
+    public FlatNodeSourceAdapter(ConfigurationSource wrappedSource)
+    {
+        if (wrappedSource == null)
+        {
+            throw new IllegalArgumentException(
+                    "ConfigurationSource to be wrapped must not be null!");
+        }
+
+        originalSource = wrappedSource;
+        nodeHandler = new FlatNodeHandler(wrappedSource);
+        wrappedSource.addConfigurationSourceListener(this);
+    }
+
+    /**
+     * Returns the original {@code ConfigurationSource} that is wrapped by this
+     * adapter.
+     *
+     * @return the original {@code ConfigurationSource}
+     */
+    public ConfigurationSource getOriginalSource()
+    {
+        return originalSource;
+    }
+
+    /**
+     * Notifies this adapter about a change in the original {@code
+     * ConfigurationSource}. This implementation invalidates the root node if
+     * this change was not caused by a change in the nodes structure.
+     *
+     * @param event the change event
+     */
+    public void configurationSourceChanged(ConfigurationSourceEvent event)
+    {
+        if (!event.isBeforeUpdate() && !nodeHandler.isInternalUpdate())
+        {
+            invalidateRootNode();
+        }
+    }
+
+    /**
+     * Adds a {@code ConfigurationSourceListener} to this source. This
+     * implementation delegates to the original source. Therefore the listener
+     * is notified each time the original source is modified. This includes
+     * updates performed by this adapter.
+     *
+     * @param l the {@code ConfigurationSourceListener} to be added
+     */
+    public void addConfigurationSourceListener(ConfigurationSourceListener l)
+    {
+        getOriginalSource().addConfigurationSourceListener(l);
+    }
+
+    /**
+     * Clears this {@code ConfigurationSource}. This implementation delegates to
+     * the original source. This also causes the hierarchy of flat nodes managed
+     * internally to be cleared.
+     */
+    public void clear()
+    {
+        getOriginalSource().clear();
+    }
+
+    /**
+     * Returns the {@code NodeHandler} for dealing with the nodes used by this
+     * {@code HierarchicalConfigurationSource}. This implementation returns a
+     * handler for {@link FlatNode} objects.
+     *
+     * @return the {@code NodeHandler} for this source
+     */
+    public NodeHandler<FlatNode> getNodeHandler()
+    {
+        return nodeHandler;
+    }
+
+    /**
+     * Returns the root node of this {@code HierarchicalConfigurationSource}. If
+     * necessary, the hierarchy of nodes is created.
+     *
+     * @return the root node of this source
+     */
+    public synchronized FlatNode getRootNode()
+    {
+        if (root == null)
+        {
+            root = constructNodeHierarchy();
+        }
+
+        return root;
+    }
+
+    /**
+     * Removes the specified {@code ConfigurationSourceListener} from this
+     * source. As is true for
+     * {@link #addConfigurationSourceListener(ConfigurationSourceListener)},
+     * this implementation delegates to the original source.
+     *
+     * @param l the {@code ConfigurationSourceListener} to be removed
+     * @return a flag whether the listener could be removed
+     */
+    public boolean removeConfigurationSourceListener(
+            ConfigurationSourceListener l)
+    {
+        return getOriginalSource().removeConfigurationSourceListener(l);
+    }
+
+    /**
+     * Sets a new root node. This operation is not supported, so an exception is
+     * thrown.
+     *
+     * @param root the new root node
+     * @throws UnsupportedOperationException as this operation is not supported
+     */
+    public void setRootNode(FlatNode root)
+    {
+        throw new UnsupportedOperationException("Not implemented!");
+    }
+
+    /**
+     * Creates a hierarchy of {@code FlatNode} objects that corresponds to the
+     * data stored in the wrapped {@code ConfigurationSource}. This
+     * implementation relies on the method {@code getKeys()} of the wrapped
+     * source to obtain the data required for constructing the node hierarchy.
+     *
+     * @return the root node of the hierarchy
+     */
+    protected FlatNode constructNodeHierarchy()
+    {
+        FlatRootNode root = new FlatRootNode();
+        for (Iterator<String> it = getOriginalSource().getKeys(); it.hasNext();)
+        {
+            String key = it.next();
+            int count = ConfigurationSourceUtils.valueCount(
+                    getOriginalSource(), key);
+            for (int i = 0; i < count; i++)
+            {
+                root.addChild(key, true);
+            }
+        }
+
+        return root;
+    }
+
+    /**
+     * Invalidates the root node of the flat nodes hierarchy. This method is
+     * called whenever a change of the wrapped source was detected. It sets the
+     * root node to <b>null</b>, so that the hierarchy has to be re-created on
+     * next access.
+     */
+    protected synchronized void invalidateRootNode()
+    {
+        root = null;
+    }
+}

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

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

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

Added: commons/proper/configuration/branches/configuration2_experimental/src/test/java/org/apache/commons/configuration2/base/TestFlatNodeSourceAdapter.java
URL: http://svn.apache.org/viewvc/commons/proper/configuration/branches/configuration2_experimental/src/test/java/org/apache/commons/configuration2/base/TestFlatNodeSourceAdapter.java?rev=807333&view=auto
==============================================================================
--- commons/proper/configuration/branches/configuration2_experimental/src/test/java/org/apache/commons/configuration2/base/TestFlatNodeSourceAdapter.java (added)
+++ commons/proper/configuration/branches/configuration2_experimental/src/test/java/org/apache/commons/configuration2/base/TestFlatNodeSourceAdapter.java Mon Aug 24 18:35:51 2009
@@ -0,0 +1,279 @@
+/*
+ * 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 java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import junit.framework.TestCase;
+
+import org.apache.commons.configuration2.expr.def.DefaultExpressionEngine;
+import org.easymock.EasyMock;
+
+/**
+ * Test class for {@code FlatNodeSourceAdapter}.
+ *
+ * @author Commons Configuration team
+ * @version $Id$
+ */
+public class TestFlatNodeSourceAdapter extends TestCase
+{
+    /** An array with the names of the test properties. */
+    private static final String[] KEYS = {
+            "db.user", "db.pwd", "db.driver", "test"
+    };
+
+    /** An array with the values of the test properties. */
+    private static final Object[] VALUES = {
+            "scott", "elephant", "test.driver", true
+    };
+
+    /** Constant for a new property key. */
+    private static final String NEW_KEY = "another.key";
+
+    /** Constant for the value of the new property. */
+    private static final Object NEW_VALUE = "new property value";
+
+    /**
+     * Creates a mock configuration source. The source is already prepared to
+     * expect a registration of a change listener.
+     *
+     * @return the mock source
+     */
+    private static ConfigurationSource createMockSource()
+    {
+        ConfigurationSource src = EasyMock
+                .createMock(ConfigurationSource.class);
+        src
+                .addConfigurationSourceListener((ConfigurationSourceListener) EasyMock
+                        .anyObject());
+        return src;
+    }
+
+    /**
+     * Creates an adapter instance that wraps a source which was already
+     * initialized with some test data.
+     *
+     * @return the adapter instance
+     */
+    private static FlatNodeSourceAdapter createTestAdapter()
+    {
+        Map<String, Object> map = new LinkedHashMap<String, Object>();
+        for (int i = 0; i < KEYS.length; i++)
+        {
+            map.put(KEYS[i], VALUES[i]);
+        }
+        ConfigurationSource src = new ConfigurationSourceEventWrapper(
+                new MapConfigurationSource(map));
+        return new FlatNodeSourceAdapter(src);
+    }
+
+    /**
+     * Creates a test configuration using the specified source. Because the
+     * source deals with flat nodes using the dot (".") as regular property
+     * character the configuration uses an expression engine with a different
+     * property separator.
+     *
+     * @param src the source
+     * @return the test configuration
+     */
+    private static Configuration<FlatNode> createTestConfig(
+            HierarchicalConfigurationSource<FlatNode> src)
+    {
+        Configuration<FlatNode> config = new ConfigurationImpl<FlatNode>(src);
+        DefaultExpressionEngine expr = new DefaultExpressionEngine();
+        expr.setPropertyDelimiter("/");
+        config.setExpressionEngine(expr);
+        return config;
+    }
+
+    /**
+     * Tries to create an adapter for a null source. This should cause an
+     * exception.
+     */
+    public void testInitNull()
+    {
+        try
+        {
+            new FlatNodeSourceAdapter(null);
+            fail("Could create instance without source!");
+        }
+        catch (IllegalArgumentException iex)
+        {
+            // ok
+        }
+    }
+
+    /**
+     * Tests whether the correct original source is returned.
+     */
+    public void testGetOriginalSource()
+    {
+        ConfigurationSource src = createMockSource();
+        EasyMock.replay(src);
+        FlatNodeSourceAdapter adapter = new FlatNodeSourceAdapter(src);
+        assertEquals("Wrong original source", src, adapter.getOriginalSource());
+        EasyMock.verify(src);
+    }
+
+    /**
+     * Tests whether a listener can be added. It should be added at the wrapped
+     * source.
+     */
+    public void testAddConfigurationSourceListener()
+    {
+        ConfigurationSource src = createMockSource();
+        ConfigurationSourceListener l = EasyMock
+                .createMock(ConfigurationSourceListener.class);
+        src.addConfigurationSourceListener(l);
+        EasyMock.replay(l, src);
+        FlatNodeSourceAdapter adapter = new FlatNodeSourceAdapter(src);
+        adapter.addConfigurationSourceListener(l);
+        EasyMock.verify(l, src);
+    }
+
+    /**
+     * Tests whether event listeners can be removed. The calls should be
+     * delegated to the wrapped source.
+     */
+    public void testRemoveConfigurationSourceListener()
+    {
+        ConfigurationSource src = createMockSource();
+        ConfigurationSourceListener l = EasyMock
+                .createMock(ConfigurationSourceListener.class);
+        EasyMock.expect(src.removeConfigurationSourceListener(l)).andReturn(
+                Boolean.TRUE);
+        EasyMock.expect(src.removeConfigurationSourceListener(l)).andReturn(
+                Boolean.FALSE);
+        EasyMock.replay(l, src);
+        FlatNodeSourceAdapter adapter = new FlatNodeSourceAdapter(src);
+        assertTrue("Wrong result for call 1", adapter
+                .removeConfigurationSourceListener(l));
+        assertFalse("Wrong result for call 2", adapter
+                .removeConfigurationSourceListener(l));
+        EasyMock.verify(l, src);
+    }
+
+    /**
+     * Tests the clear() implementation.
+     */
+    public void testClear()
+    {
+        ConfigurationSource src = createMockSource();
+        src.clear();
+        EasyMock.replay(src);
+        FlatNodeSourceAdapter adapter = new FlatNodeSourceAdapter(src);
+        adapter.clear();
+        EasyMock.verify(src);
+    }
+
+    /**
+     * Tries to set a root node. This operation is not supported, so an
+     * exception should be thrown.
+     */
+    public void testSetRootNode()
+    {
+        ConfigurationSource src = createMockSource();
+        EasyMock.replay(src);
+        FlatNodeSourceAdapter adapter = new FlatNodeSourceAdapter(src);
+        try
+        {
+            adapter.setRootNode(new FlatRootNode());
+            fail("Could set a new root node!");
+        }
+        catch (UnsupportedOperationException uex)
+        {
+            EasyMock.verify(src);
+        }
+    }
+
+    /**
+     * Tests whether the data of the wrapped configuration source is actually
+     * transformed into a hierarchical structure. We test whether it can be
+     * accessed from a configuration.
+     */
+    public void testTransformation()
+    {
+        FlatNodeSourceAdapter adapter = createTestAdapter();
+        Configuration<FlatNode> config = createTestConfig(adapter);
+        Iterator<String> it = config.getKeys();
+        int idx = 0;
+        while (it.hasNext())
+        {
+            String key = it.next();
+            assertEquals("Wrong key at " + idx, KEYS[idx], key);
+            assertEquals("Wrong value for " + key, VALUES[idx], config
+                    .getProperty(key));
+            idx++;
+        }
+        assertEquals("Wrong number of keys", KEYS.length, idx);
+    }
+
+    /**
+     * Tests the transformation performed by the adapter if a property with
+     * multiple values is involved.
+     */
+    public void testTransformationList()
+    {
+        FlatNodeSourceAdapter adapter = createTestAdapter();
+        final int count = 5;
+        for (int i = 0; i < count; i++)
+        {
+            adapter.getOriginalSource().addProperty(NEW_KEY, i);
+        }
+        Configuration<FlatNode> config = createTestConfig(adapter);
+        List<?> values = config.getList(NEW_KEY);
+        assertEquals("Wrong number of values", count, values.size());
+        for (int i = 0; i < count; i++)
+        {
+            assertEquals("Wrong value at " + i, Integer.valueOf(i), values
+                    .get(i));
+        }
+    }
+
+    /**
+     * Tests whether changes at the wrapped source are visible.
+     */
+    public void testConfigurationSourceChanged()
+    {
+        FlatNodeSourceAdapter adapter = createTestAdapter();
+        Configuration<FlatNode> config = createTestConfig(adapter);
+        FlatNode root = adapter.getRootNode();
+        assertFalse("Key already found", config.containsKey(NEW_KEY));
+        adapter.getOriginalSource().addProperty(NEW_KEY, NEW_VALUE);
+        assertEquals("Wrong value for new property", NEW_VALUE, config
+                .getProperty(NEW_KEY));
+        assertNotSame("Root node not changed", root, adapter.getRootNode());
+    }
+
+    /**
+     * Tests whether updates of the configuration are visible in the wrapped
+     * source.
+     */
+    public void testUpdateOriginalSource()
+    {
+        FlatNodeSourceAdapter adapter = createTestAdapter();
+        Configuration<FlatNode> config = createTestConfig(adapter);
+        FlatNode root = adapter.getRootNode();
+        config.addProperty(NEW_KEY, NEW_VALUE);
+        assertEquals("Root node was changed", root, adapter.getRootNode());
+        assertEquals("Property not found in source", NEW_VALUE, adapter
+                .getOriginalSource().getProperty(NEW_KEY));
+    }
+}

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

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

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