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 "flat"
+ * {@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