Author: oheger
Date: Sat Aug 8 15:04:21 2009
New Revision: 802387
URL: http://svn.apache.org/viewvc?rev=802387&view=rev
Log:
Added ConfigurationSourceEventWrapper class which allows adding support for
event notifications to an arbitrary ConfigurationSource implementation.
Added:
commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/base/ConfigurationSourceEventWrapper.java
(with props)
commons/proper/configuration/branches/configuration2_experimental/src/test/java/org/apache/commons/configuration2/base/TestConfigurationSourceEventWrapper.java
(with props)
Added:
commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/base/ConfigurationSourceEventWrapper.java
URL:
http://svn.apache.org/viewvc/commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/base/ConfigurationSourceEventWrapper.java?rev=802387&view=auto
==============================================================================
---
commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/base/ConfigurationSourceEventWrapper.java
(added)
+++
commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/base/ConfigurationSourceEventWrapper.java
Sat Aug 8 15:04:21 2009
@@ -0,0 +1,281 @@
+/*
+ * 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.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+/**
+ * <p>
+ * A specialized implementation of {...@code ConfigurationSource} that allows
+ * adding support for event notifications to a wrapped {...@code
+ * ConfigurationSource}.
+ * </p>
+ * <p>
+ * This class is a wrapper around another {...@code ConfigurationSource}. The
+ * methods defined by the {...@code ConfigurationSource} interface are - to the
+ * major part - implemented by delegating to the wrapped source. If a method
+ * requires firing an event, the event is fired by this class before and after
+ * the wrapped source is invoked. This way support for event notifications can
+ * be added to arbitrary {...@code ConfigurationSource} implementations
+ * transparently.
+ * </p>
+ *
+ * @author Commons Configuration team
+ * @version $Id$
+ */
+public class ConfigurationSourceEventWrapper implements ConfigurationSource
+{
+ /** Stores the wrapped configuration source. */
+ private final ConfigurationSource wrappedSource;
+
+ /** The list with the event listeners that have been registered. */
+ private final List<ConfigurationSourceListener> listeners;
+
+ /**
+ * Creates a new instance of {...@code ConfigurationSourceEventWrapper} and
+ * sets the source to be wrapped.
+ *
+ * @param wrapped the source to be wrapped (must not be <b>null</b>)
+ * @throws IllegalArgumentException if the wrapped source is <b>null</b>
+ */
+ public ConfigurationSourceEventWrapper(ConfigurationSource wrapped)
+ {
+ if (wrapped == null)
+ {
+ throw new IllegalArgumentException(
+ "Wrapped source must not be null!");
+ }
+
+ wrappedSource = wrapped;
+ listeners = new CopyOnWriteArrayList<ConfigurationSourceListener>();
+ }
+
+ /**
+ * Returns the {...@code ConfigurationSource} that is wrapped by this
object.
+ *
+ * @return the wrapped source
+ */
+ public ConfigurationSource getWrappedSource()
+ {
+ return wrappedSource;
+ }
+
+ /**
+ * Adds a new {...@code ConfigurationSourceListener} to this source.
+ * Implementation note: It is safe to call this method concurrently from
+ * multiple threads.
+ *
+ * @param l the listener to be added (must not be <b>null</b>)
+ * @throws IllegalArgumentException if the listener is <b>null</b>
+ */
+ public void addConfigurationSourceListener(ConfigurationSourceListener l)
+ {
+ if (l == null)
+ {
+ throw new IllegalArgumentException(
+ "ConfigurationSourceListener must not be null!");
+ }
+
+ listeners.add(l);
+ }
+
+ /**
+ * Adds a new property to this {...@code ConfigurationSource}. This method
+ * delegates to the wrapped source. It also produces the correct events for
+ * adding a property.
+ *
+ * @param key the key of the new property
+ * @param value the value of the new property
+ */
+ public void addProperty(String key, Object value)
+ {
+ fireEvent(ConfigurationSourceEvent.Type.ADD_PROPERTY, key, value,
true);
+ getWrappedSource().addProperty(key, value);
+ fireEvent(ConfigurationSourceEvent.Type.ADD_PROPERTY, key, value,
false);
+ }
+
+ /**
+ * Clears this {...@code ConfigurationSource}. This implementation
delegates to
+ * the wrapped source. It also produces the correct events for clearing the
+ * source.
+ */
+ public void clear()
+ {
+ fireEvent(ConfigurationSourceEvent.Type.CLEAR_SOURCE, null, null,
true);
+ getWrappedSource().clear();
+ fireEvent(ConfigurationSourceEvent.Type.CLEAR_SOURCE, null, null,
false);
+ }
+
+ /**
+ * Removes the specified property from this {...@code ConfigurationSource}.
+ * This implementation delegates to the wrapped source. It also produces
the
+ * correct events for removing a property.
+ *
+ * @param key the key of the property to be removed
+ */
+ public void clearProperty(String key)
+ {
+ fireEvent(ConfigurationSourceEvent.Type.CLEAR_PROPERTY, key, null,
true);
+ getWrappedSource().clearProperty(key);
+ fireEvent(ConfigurationSourceEvent.Type.CLEAR_PROPERTY, key, null,
+ false);
+ }
+
+ /**
+ * Tests whether the specified key is contained in this {...@code
+ * ConfigurationSource}. This implementation delegates to the wrapped
+ * source.
+ *
+ * @param key the key in question
+ * @return a flag whether this key is contained in this {...@code
+ * ConfigurationSource}
+ */
+ public boolean containsKey(String key)
+ {
+ return getWrappedSource().containsKey(key);
+ }
+
+ /**
+ * Returns an iterator with all keys contained in this {...@code
+ * ConfigurationSource}. This implementation delegates to the wrapped
+ * source.
+ *
+ * @return an iterator with the keys of this {...@code ConfigurationSource}
+ */
+ public Iterator<String> getKeys()
+ {
+ return getWrappedSource().getKeys();
+ }
+
+ /**
+ * Returns an iterator with all keys contained in this {...@code
+ * ConfigurationSource} starting with the given prefix. This implementation
+ * delegates to the wrapped source.
+ *
+ * @param prefix the prefix of the searched keys
+ * @return an iterator with all keys starting with this prefix
+ */
+ public Iterator<String> getKeys(String prefix)
+ {
+ return getWrappedSource().getKeys(prefix);
+ }
+
+ /**
+ * Returns the value of the specified property. This implementation
+ * delegates to the wrapped source.
+ *
+ * @param key the key of the property
+ * @return the value of this property or <b>null</b> if it cannot be found
+ */
+ public Object getProperty(String key)
+ {
+ return getWrappedSource().getProperty(key);
+ }
+
+ /**
+ * Tests whether this {...@code ConfigurationSource} is empty. This
+ * implementation delegates to the wrapped source.
+ *
+ * @return <b>true</b> if this {...@code ConfigurationSource} is empty,
+ * <b>false</b> otherwise
+ */
+ public boolean isEmpty()
+ {
+ return getWrappedSource().isEmpty();
+ }
+
+ /**
+ * Removes the specified {...@code ConfigurationSourceListener} from this
+ * {...@code ConfigurationSource}.
+ *
+ * @param l the listener to be removed
+ * @return a flag whether this listener could be removed
+ */
+ public boolean removeConfigurationSourceListener(
+ ConfigurationSourceListener l)
+ {
+ return listeners.remove(l);
+ }
+
+ /**
+ * Sets the value of a property. This implementation delegates to the
+ * wrapped source. It also produces the correct events for modifying a
+ * property.
+ *
+ * @param key the key of the property to be set
+ * @param value the new value of this property
+ */
+ public void setProperty(String key, Object value)
+ {
+ fireEvent(ConfigurationSourceEvent.Type.MODIFY_PROPERTY, key, value,
+ true);
+ getWrappedSource().setProperty(key, value);
+ fireEvent(ConfigurationSourceEvent.Type.MODIFY_PROPERTY, key, value,
+ false);
+ }
+
+ /**
+ * Returns the size of this {...@code ConfigurationSource}. This
implementation
+ * delegates to the wrapped source.
+ *
+ * @return the size of this {...@code ConfigurationSource}
+ */
+ public int size()
+ {
+ return getWrappedSource().size();
+ }
+
+ /**
+ * Returns the number of values stored for the property with the given key.
+ * This implementation delegates to the wrapped source.
+ *
+ * @param key the key of the property in question
+ * @return the number of values stored for this property
+ */
+ public int valueCount(String key)
+ {
+ return getWrappedSource().valueCount(key);
+ }
+
+ /**
+ * Helper method for sending an event to all listeners currently registered
+ * at this object.
+ *
+ * @param type the type of the event
+ * @param key the property key
+ * @param value the property value
+ * @param before the before update flag
+ */
+ private void fireEvent(ConfigurationSourceEvent.Type type, String key,
+ Object value, boolean before)
+ {
+ ConfigurationSourceEvent event = null; // lazy creation
+
+ for (ConfigurationSourceListener l : listeners)
+ {
+ if (event == null)
+ {
+ event = new ConfigurationSourceEvent(this, type, key, value,
+ null, before);
+ }
+
+ l.configurationSourceChanged(event);
+ }
+ }
+}
Propchange:
commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/base/ConfigurationSourceEventWrapper.java
------------------------------------------------------------------------------
svn:eol-style = native
Propchange:
commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/base/ConfigurationSourceEventWrapper.java
------------------------------------------------------------------------------
svn:keywords = Date Author Id Revision HeadURL
Propchange:
commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/base/ConfigurationSourceEventWrapper.java
------------------------------------------------------------------------------
svn:mime-type = text/plain
Added:
commons/proper/configuration/branches/configuration2_experimental/src/test/java/org/apache/commons/configuration2/base/TestConfigurationSourceEventWrapper.java
URL:
http://svn.apache.org/viewvc/commons/proper/configuration/branches/configuration2_experimental/src/test/java/org/apache/commons/configuration2/base/TestConfigurationSourceEventWrapper.java?rev=802387&view=auto
==============================================================================
---
commons/proper/configuration/branches/configuration2_experimental/src/test/java/org/apache/commons/configuration2/base/TestConfigurationSourceEventWrapper.java
(added)
+++
commons/proper/configuration/branches/configuration2_experimental/src/test/java/org/apache/commons/configuration2/base/TestConfigurationSourceEventWrapper.java
Sat Aug 8 15:04:21 2009
@@ -0,0 +1,364 @@
+/*
+ * 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.Collections;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+
+import junit.framework.TestCase;
+
+import org.easymock.EasyMock;
+
+/**
+ * Test class for {...@code ConfigurationSourceEventWrapper}.
+ *
+ * @author Commons Configuration team
+ * @version $Id$
+ */
+public class TestConfigurationSourceEventWrapper extends TestCase
+{
+ /** Constant for a property name. */
+ private static final String PROPERTY = "testProperty";
+
+ /** Constant for a test value. */
+ private static final Object VALUE = 42;
+
+ /** Stores a mock for the wrapped source. */
+ private ConfigurationSource wrappedSource;
+
+ /** A test listener. */
+ private ConfigurationSourceListenerImpl listener;
+
+ /** The source to be tested. */
+ private ConfigurationSourceEventWrapper wrapper;
+
+ @Override
+ protected void setUp() throws Exception
+ {
+ wrappedSource = EasyMock.createMock(ConfigurationSource.class);
+ wrapper = new ConfigurationSourceEventWrapper(wrappedSource);
+ listener = new ConfigurationSourceListenerImpl();
+ wrapper.addConfigurationSourceListener(listener);
+ }
+
+ /**
+ * Tests whether the correct wrapped source is returned.
+ */
+ public void testGetWrappedSource()
+ {
+ assertEquals("Wrong wrapped source", wrappedSource, wrapper
+ .getWrappedSource());
+ }
+
+ /**
+ * Tries to create an instance without a wrapped source. This should cause
+ * an exception.
+ */
+ public void testInitNullSource()
+ {
+ try
+ {
+ new ConfigurationSourceEventWrapper(null);
+ fail("Could create instance without a wrapped source!");
+ }
+ catch (IllegalArgumentException iex)
+ {
+ // ok
+ }
+ }
+
+ /**
+ * Tests whether a property can be added and the expected events are
+ * generated.
+ */
+ public void testAddProperty()
+ {
+ wrappedSource.addProperty(PROPERTY, VALUE);
+ EasyMock.replay(wrappedSource);
+ wrapper.addProperty(PROPERTY, VALUE);
+ EasyMock.verify(wrappedSource);
+ listener.checkEvent(ConfigurationSourceEvent.Type.ADD_PROPERTY,
+ PROPERTY, VALUE);
+ listener.checkDone();
+ }
+
+ /**
+ * Tests the clear() implementation and the events generated by it.
+ */
+ public void testClear()
+ {
+ wrappedSource.clear();
+ EasyMock.replay(wrappedSource);
+ wrapper.clear();
+ EasyMock.verify(wrappedSource);
+ listener.checkEvent(ConfigurationSourceEvent.Type.CLEAR_SOURCE, null,
+ null);
+ listener.checkDone();
+ }
+
+ /**
+ * Tests whether a property can be removed and the expected events are
+ * generated.
+ */
+ public void testClearProperty()
+ {
+ wrappedSource.clearProperty(PROPERTY);
+ EasyMock.replay(wrappedSource);
+ wrapper.clearProperty(PROPERTY);
+ EasyMock.verify(wrappedSource);
+ listener.checkEvent(ConfigurationSourceEvent.Type.CLEAR_PROPERTY,
+ PROPERTY, null);
+ listener.checkDone();
+ }
+
+ /**
+ * Tests the containsKey() implementation.
+ */
+ public void testContainsKey()
+ {
+ EasyMock.expect(wrappedSource.containsKey(PROPERTY)).andReturn(
+ Boolean.TRUE);
+ EasyMock.expect(wrappedSource.containsKey(PROPERTY)).andReturn(
+ Boolean.FALSE);
+ EasyMock.replay(wrappedSource);
+ assertTrue("Wrong result (1)", wrapper.containsKey(PROPERTY));
+ assertFalse("Wrong result (2)", wrapper.containsKey(PROPERTY));
+ listener.checkDone();
+ }
+
+ /**
+ * Tests the getKeys() implementation.
+ */
+ public void testGetKeys()
+ {
+ List<String> keyList = Collections.singletonList(PROPERTY);
+ Iterator<String> it = keyList.iterator();
+ EasyMock.expect(wrappedSource.getKeys()).andReturn(it);
+ EasyMock.replay(wrappedSource);
+ assertSame("Wrong iterator", it, wrapper.getKeys());
+ EasyMock.verify(wrappedSource);
+ listener.checkDone();
+ }
+
+ /**
+ * Tests the getKeys() implementation that takes a prefix.
+ */
+ public void testGetKeysPrefix()
+ {
+ List<String> keyList = Collections.singletonList(PROPERTY);
+ Iterator<String> it = keyList.iterator();
+ EasyMock.expect(wrappedSource.getKeys(PROPERTY)).andReturn(it);
+ EasyMock.replay(wrappedSource);
+ assertSame("Wrong iterator", it, wrapper.getKeys(PROPERTY));
+ EasyMock.verify(wrappedSource);
+ listener.checkDone();
+ }
+
+ /**
+ * Tests whether a property can be queried.
+ */
+ public void testGetProperty()
+ {
+ EasyMock.expect(wrappedSource.getProperty(PROPERTY)).andReturn(VALUE);
+ EasyMock.replay(wrappedSource);
+ assertEquals("Wrong value", VALUE, wrapper.getProperty(PROPERTY));
+ EasyMock.verify(wrappedSource);
+ listener.checkDone();
+ }
+
+ /**
+ * Tests the isEmpty() implementation.
+ */
+ public void testIsEmpty()
+ {
+ EasyMock.expect(wrappedSource.isEmpty()).andReturn(Boolean.TRUE);
+ EasyMock.expect(wrappedSource.isEmpty()).andReturn(Boolean.FALSE);
+ EasyMock.replay(wrappedSource);
+ assertTrue("Wrong result (1)", wrapper.isEmpty());
+ assertFalse("Wrong result (2)", wrapper.isEmpty());
+ EasyMock.verify(wrappedSource);
+ listener.checkDone();
+ }
+
+ /**
+ * Tests whether a property can be set and whether the correct events are
+ * generated.
+ */
+ public void testSetProperty()
+ {
+ wrappedSource.setProperty(PROPERTY, VALUE);
+ EasyMock.replay(wrappedSource);
+ wrapper.setProperty(PROPERTY, VALUE);
+ EasyMock.verify(wrappedSource);
+ listener.checkEvent(ConfigurationSourceEvent.Type.MODIFY_PROPERTY,
+ PROPERTY, VALUE);
+ listener.checkDone();
+ }
+
+ /**
+ * Tests the size() implementation.
+ */
+ public void testSize()
+ {
+ final int size = 112;
+ EasyMock.expect(wrappedSource.size()).andReturn(size);
+ EasyMock.replay(wrappedSource);
+ assertEquals("Wrong size", size, wrapper.size());
+ EasyMock.verify(wrappedSource);
+ listener.checkDone();
+ }
+
+ /**
+ * Tests the valueCount() implementation.
+ */
+ public void testValueCount()
+ {
+ final int count = 3;
+ EasyMock.expect(wrappedSource.valueCount(PROPERTY)).andReturn(count);
+ EasyMock.replay(wrappedSource);
+ assertEquals("Wrong count", count, wrapper.valueCount(PROPERTY));
+ EasyMock.verify(wrappedSource);
+ listener.checkDone();
+ }
+
+ /**
+ * Tries to add a null listener. This should cause an exception.
+ */
+ public void testAddConfigurationSourceListenerNull()
+ {
+ try
+ {
+ wrapper.addConfigurationSourceListener(null);
+ fail("Could add a null listener!");
+ }
+ catch (IllegalArgumentException iex)
+ {
+ // ok
+ }
+ }
+
+ /**
+ * Tests whether listeners can be removed.
+ */
+ public void testRemoveConfigurationSourceListener()
+ {
+ wrappedSource.clearProperty(PROPERTY);
+ wrappedSource.addProperty(PROPERTY, VALUE);
+ wrappedSource.clear();
+ EasyMock.replay(wrappedSource);
+ ConfigurationSourceListenerImpl l = new
ConfigurationSourceListenerImpl();
+ wrapper.addConfigurationSourceListener(l);
+ wrapper.clearProperty(PROPERTY);
+ assertTrue("Cannot remove listener (1)", wrapper
+ .removeConfigurationSourceListener(l));
+ wrapper.addProperty(PROPERTY, VALUE);
+ assertTrue("Cannot remove listener (2)", wrapper
+ .removeConfigurationSourceListener(listener));
+ wrapper.clear();
+ EasyMock.verify(wrappedSource);
+ l.checkEvent(ConfigurationSourceEvent.Type.CLEAR_PROPERTY, PROPERTY,
+ null);
+ l.checkDone();
+ listener.checkEvent(ConfigurationSourceEvent.Type.CLEAR_PROPERTY,
+ PROPERTY, null);
+ listener.checkEvent(ConfigurationSourceEvent.Type.ADD_PROPERTY,
+ PROPERTY, VALUE);
+ listener.checkDone();
+ }
+
+ /**
+ * Tries to remove a listener that was not registered.
+ */
+ public void testRemoveConfigurationSourceListenerNonExisting()
+ {
+ assertTrue("Could not remove listener", wrapper
+ .removeConfigurationSourceListener(listener));
+ assertFalse("Could remove listener again", wrapper
+ .removeConfigurationSourceListener(listener));
+ assertFalse("Could remove null listener", wrapper
+ .removeConfigurationSourceListener(null));
+ }
+
+ /**
+ * A test configuration source listener implementation used for testing the
+ * events generated by the source.
+ */
+ private class ConfigurationSourceListenerImpl implements
+ ConfigurationSourceListener
+ {
+ /** A list with the received events. */
+ private final List<ConfigurationSourceEvent> events = new
LinkedList<ConfigurationSourceEvent>();
+
+ /**
+ * Records this invocation.
+ */
+ public void configurationSourceChanged(ConfigurationSourceEvent event)
+ {
+ events.add(event);
+ }
+
+ /**
+ * Tests the next pair of events received by this listener. This method
+ * expects that there are two identical events in sequence that only
+ * differ in their before update flag.
+ *
+ * @param type the expected event type
+ * @param prop the expected property name
+ * @param value the expected property value
+ */
+ public void checkEvent(ConfigurationSourceEvent.Type type, String prop,
+ Object value)
+ {
+ checkEvent(type, prop, value, true);
+ checkEvent(type, prop, value, false);
+ }
+
+ /**
+ * Tests the next event received by this listener. The properties are
+ * compared to the event's data.
+ *
+ * @param type the expected event type
+ * @param prop the expected property name
+ * @param value the expected property value
+ * @param before the before update flag
+ */
+ public void checkEvent(ConfigurationSourceEvent.Type type, String prop,
+ Object value, boolean before)
+ {
+ assertFalse("Too few events", events.isEmpty());
+ ConfigurationSourceEvent event = events.remove(0);
+ assertEquals("Wrong source", wrapper, event.getSource());
+ assertEquals("Wrong type", type, event.getType());
+ assertEquals("Wrong property", prop, event.getPropertyName());
+ assertEquals("Wrong value", value, event.getPropertyValue());
+ assertEquals("Wrong before update flag", before, event
+ .isBeforeUpdate());
+ assertNull("Got additional data", event.getData());
+ }
+
+ /**
+ * Tests whether all events have been checked. This method should be
+ * called to verify that there are no additional events.
+ */
+ public void checkDone()
+ {
+ assertTrue("Too many events: " + events, events.isEmpty());
+ }
+ }
+}
Propchange:
commons/proper/configuration/branches/configuration2_experimental/src/test/java/org/apache/commons/configuration2/base/TestConfigurationSourceEventWrapper.java
------------------------------------------------------------------------------
svn:eol-style = native
Propchange:
commons/proper/configuration/branches/configuration2_experimental/src/test/java/org/apache/commons/configuration2/base/TestConfigurationSourceEventWrapper.java
------------------------------------------------------------------------------
svn:keywords = Date Author Id Revision HeadURL
Propchange:
commons/proper/configuration/branches/configuration2_experimental/src/test/java/org/apache/commons/configuration2/base/TestConfigurationSourceEventWrapper.java
------------------------------------------------------------------------------
svn:mime-type = text/plain