You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@commons.apache.org by oh...@apache.org on 2007/01/13 17:33:03 UTC
svn commit: r495918 - in /jakarta/commons/proper/configuration/trunk/src:
java/org/apache/commons/configuration/
java/org/apache/commons/configuration/event/
test/org/apache/commons/configuration/event/
Author: oheger
Date: Sat Jan 13 08:33:02 2007
New Revision: 495918
URL: http://svn.apache.org/viewvc?view=rev&rev=495918
Log:
CONFIGURATION-245: Added support for ConfigurationErrorListeners to EventSource
Modified:
jakarta/commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/BaseConfiguration.java
jakarta/commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/HierarchicalConfiguration.java
jakarta/commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/event/EventSource.java
jakarta/commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/event/TestEventSource.java
Modified: jakarta/commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/BaseConfiguration.java
URL: http://svn.apache.org/viewvc/jakarta/commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/BaseConfiguration.java?view=diff&rev=495918&r1=495917&r2=495918
==============================================================================
--- jakarta/commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/BaseConfiguration.java (original)
+++ jakarta/commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/BaseConfiguration.java Sat Jan 13 08:33:02 2007
@@ -168,7 +168,6 @@
{
BaseConfiguration copy = (BaseConfiguration) super.clone();
copy.store = (Map) ConfigurationUtils.clone(store);
- copy.clearConfigurationListeners();
return copy;
}
catch (CloneNotSupportedException cex)
Modified: jakarta/commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/HierarchicalConfiguration.java
URL: http://svn.apache.org/viewvc/jakarta/commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/HierarchicalConfiguration.java?view=diff&rev=495918&r1=495917&r2=495918
==============================================================================
--- jakarta/commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/HierarchicalConfiguration.java (original)
+++ jakarta/commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/HierarchicalConfiguration.java Sat Jan 13 08:33:02 2007
@@ -699,7 +699,6 @@
CloneVisitor v = new CloneVisitor();
getRootNode().visit(v);
copy.setRootNode(v.getClone());
- copy.clearConfigurationListeners();
return copy;
}
Modified: jakarta/commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/event/EventSource.java
URL: http://svn.apache.org/viewvc/jakarta/commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/event/EventSource.java?view=diff&rev=495918&r1=495917&r2=495918
==============================================================================
--- jakarta/commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/event/EventSource.java (original)
+++ jakarta/commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/event/EventSource.java Sat Jan 13 08:33:02 2007
@@ -50,8 +50,17 @@
* events will be received. Note that the number of received detail events may
* differ for different configuration implementations.
* <code>{@link org.apache.commons.configuration.HierarchicalConfiguration HierarchicalConfiguration}</code>
- * for instance has a custom implementation of <code>setProperty()</code>, which
- * does not generate any detail events.
+ * for instance has a custom implementation of <code>setProperty()</code>,
+ * which does not generate any detail events.
+ * </p>
+ * <p>
+ * In addition to "normal" events, error events are supported. Such
+ * events signal an internal problem that occurred during access of properties.
+ * For them a special listener interface exists:
+ * <code>{@link ConfigurationErrorListener}</code>. There is another set of
+ * methods dealing with event listeners of this type. The
+ * <code>fireError()</code> method can be used by derived classes to send
+ * notifications about errors to registered observers.
* </p>
*
* @author <a
@@ -65,6 +74,9 @@
/** A collection for the registered event listeners. */
private Collection listeners;
+ /** A collection for the registered error listeners.*/
+ private Collection errorListeners;
+
/** A counter for the detail events. */
private int detailEvents;
@@ -73,7 +85,7 @@
*/
public EventSource()
{
- clearConfigurationListeners();
+ initListeners();
}
/**
@@ -83,14 +95,7 @@
*/
public void addConfigurationListener(ConfigurationListener l)
{
- if (l == null)
- {
- throw new IllegalArgumentException("Listener must not be null!");
- }
- synchronized (listeners)
- {
- listeners.add(l);
- }
+ doAddListener(listeners, l);
}
/**
@@ -102,10 +107,7 @@
*/
public boolean removeConfigurationListener(ConfigurationListener l)
{
- synchronized (listeners)
- {
- return listeners.remove(l);
- }
+ return doRemoveListener(listeners, l);
}
/**
@@ -113,15 +115,13 @@
* currently registered at this object.
*
* @return a collection with the registered
- * <code>ConfigurationListener</code>s (this collection cannot be
- * changed)
+ * <code>ConfigurationListener</code>s (this collection is a snapshot
+ * of the currently registered listeners; manipulating it has no effect
+ * on this event source object)
*/
public Collection getConfigurationListeners()
{
- synchronized (listeners)
- {
- return Collections.unmodifiableCollection(listeners);
- }
+ return doGetListeners(listeners);
}
/**
@@ -129,7 +129,7 @@
*/
public void clearConfigurationListeners()
{
- listeners = new LinkedList();
+ doClearListeners(listeners);
}
/**
@@ -170,6 +170,55 @@
}
/**
+ * Adds a new configuration error listener to this object. This listener
+ * will then be notified about internal problems.
+ *
+ * @param l the listener to register (must not be <b>null</b>)
+ * @since 1.4
+ */
+ public void addErrorListener(ConfigurationErrorListener l)
+ {
+ doAddListener(errorListeners, l);
+ }
+
+ /**
+ * Removes the specified error listener so that it does not receive any
+ * further events caused by this object.
+ *
+ * @param l the listener to remove
+ * @return a flag whether the listener could be found and removed
+ * @since 1.4
+ */
+ public boolean removeErrorListener(ConfigurationErrorListener l)
+ {
+ return doRemoveListener(errorListeners, l);
+ }
+
+ /**
+ * Removes all registered error listeners.
+ *
+ * @since 1.4
+ */
+ public void clearErrorListeners()
+ {
+ doClearListeners(errorListeners);
+ }
+
+ /**
+ * Returns a collection with all configuration error listeners that are
+ * currently registered at this object.
+ *
+ * @return a collection with the registered
+ * <code>ConfigurationErrorListener</code>s (this collection is a
+ * snapshot of the currently registered listeners; it cannot be manipulated)
+ * @since 1.4
+ */
+ public Collection getErrorListeners()
+ {
+ return doGetListeners(errorListeners);
+ }
+
+ /**
* Creates an event object and delivers it to all registered event
* listeners. The method will check first if sending an event is allowed
* (making use of the <code>detailEvents</code> property), and if
@@ -221,5 +270,150 @@
Object propValue, boolean before)
{
return new ConfigurationEvent(this, type, propName, propValue, before);
+ }
+
+ /**
+ * Creates an error event object and delivers it to all registered error
+ * listeners.
+ *
+ * @param type the event's type
+ * @param propName the name of the affected property (can be <b>null</b>)
+ * @param propValue the value of the affected property (can be <b>null</b>)
+ * @param ex the <code>Throwable</code> object that caused this error
+ * event
+ * @since 1.4
+ */
+ protected void fireError(int type, String propName, Object propValue,
+ Throwable ex)
+ {
+ Collection listenersToCall = null;
+
+ synchronized (errorListeners)
+ {
+ if (errorListeners.size() > 0)
+ {
+ // Copy listeners to another collection so that manipulating
+ // the listener list during event delivery won't cause problems
+ listenersToCall = new ArrayList(errorListeners);
+ }
+ }
+
+ if (listenersToCall != null)
+ {
+ ConfigurationErrorEvent event = createErrorEvent(type, propName,
+ propValue, ex);
+ for (Iterator it = listenersToCall.iterator(); it.hasNext();)
+ {
+ ((ConfigurationErrorListener) it.next())
+ .configurationError(event);
+ }
+ }
+ }
+
+ /**
+ * Creates a <code>ConfigurationErrorEvent</code> object based on the
+ * passed in parameters. This is called by <code>fireError()</code> if it
+ * decides that an event needs to be generated.
+ *
+ * @param type the event's type
+ * @param propName the name of the affected property (can be <b>null</b>)
+ * @param propValue the value of the affected property (can be <b>null</b>)
+ * @param ex the <code>Throwable</code> object that caused this error
+ * event
+ * @return the event object
+ * @since 1.4
+ */
+ protected ConfigurationErrorEvent createErrorEvent(int type,
+ String propName, Object propValue, Throwable ex)
+ {
+ return new ConfigurationErrorEvent(this, type, propName, propValue, ex);
+ }
+
+ /**
+ * Overrides the <code>clone()</code> method to correctly handle so far
+ * registered event listeners. This implementation ensures that the clone
+ * will have empty event listener lists, i.e. the listeners registered at an
+ * <code>EventSource</code> object will not be copied.
+ *
+ * @return the cloned object
+ * @throws CloneNotSupportedException if cloning is not allowed
+ * @since 1.4
+ */
+ protected Object clone() throws CloneNotSupportedException
+ {
+ EventSource copy = (EventSource) super.clone();
+ copy.initListeners();
+ return copy;
+ }
+
+ /**
+ * Adds a new listener object to a listener collection. This is done in a
+ * synchronized block. The listener must not be <b>null</b>.
+ *
+ * @param listeners the collection with the listeners
+ * @param l the listener object
+ */
+ private static void doAddListener(Collection listeners, Object l)
+ {
+ if (l == null)
+ {
+ throw new IllegalArgumentException("Listener must not be null!");
+ }
+ synchronized (listeners)
+ {
+ listeners.add(l);
+ }
+ }
+
+ /**
+ * Removes an event listener from a listener collection. This is done in a
+ * synchronized block.
+ *
+ * @param listeners the collection with the listeners
+ * @param l the listener object
+ * @return a flag whether the listener could be found and removed
+ */
+ private static boolean doRemoveListener(Collection listeners, Object l)
+ {
+ synchronized (listeners)
+ {
+ return listeners.remove(l);
+ }
+ }
+
+ /**
+ * Removes all entries from the given list of event listeners.
+ *
+ * @param listeners the collection with the listeners
+ */
+ private static void doClearListeners(Collection listeners)
+ {
+ synchronized (listeners)
+ {
+ listeners.clear();
+ }
+ }
+
+ /**
+ * Returns an unmodifiable snapshot of the given event listener collection.
+ *
+ * @param listeners the collection with the listeners
+ * @return a snapshot of the listeners collection
+ */
+ private static Collection doGetListeners(Collection listeners)
+ {
+ synchronized (listeners)
+ {
+ return Collections.unmodifiableCollection(new ArrayList(listeners));
+ }
+ }
+
+ /**
+ * Initializes the collections for storing registered event listeners.
+ */
+ private void initListeners()
+ {
+ listeners = new LinkedList();
+ errorListeners = new LinkedList();
}
}
Modified: jakarta/commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/event/TestEventSource.java
URL: http://svn.apache.org/viewvc/jakarta/commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/event/TestEventSource.java?view=diff&rev=495918&r1=495917&r2=495918
==============================================================================
--- jakarta/commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/event/TestEventSource.java (original)
+++ jakarta/commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/event/TestEventSource.java Sat Jan 13 08:33:02 2007
@@ -55,6 +55,8 @@
assertFalse("Removing listener", source
.removeConfigurationListener(new TestListener()));
assertFalse("Detail events are enabled", source.isDetailEvents());
+ assertTrue("Error listeners list is not empty", source
+ .getErrorListeners().isEmpty());
}
/**
@@ -135,6 +137,17 @@
}
/**
+ * Tests that the collection returned by getConfigurationListeners() is
+ * really a snapshot. A later added listener must not be visible.
+ */
+ public void testGetConfigurationListenersAddNew()
+ {
+ Collection list = source.getConfigurationListeners();
+ source.addConfigurationListener(new TestListener());
+ assertTrue("Listener snapshot not empty", list.isEmpty());
+ }
+
+ /**
* Tests enabling and disabling the detail events flag.
*/
public void testSetDetailEvents()
@@ -212,36 +225,175 @@
}
/**
+ * Tests registering a new error listener.
+ */
+ public void testAddErrorListener()
+ {
+ TestListener l = new TestListener();
+ source.addErrorListener(l);
+ Collection listeners = source.getErrorListeners();
+ assertEquals("Wrong number of listeners", 1, listeners.size());
+ assertTrue("Listener not in list", listeners.contains(l));
+ }
+
+ /**
+ * Tests adding an undefined error listener. This should cause an exception.
+ */
+ public void testAddNullErrorListener()
+ {
+ try
+ {
+ source.addErrorListener(null);
+ fail("Could add null error listener!");
+ }
+ catch (IllegalArgumentException iex)
+ {
+ // ok
+ }
+ }
+
+ /**
+ * Tests removing an error listener.
+ */
+ public void testRemoveErrorListener()
+ {
+ TestListener l = new TestListener();
+ assertFalse("Listener can be removed?", source.removeErrorListener(l));
+ source.addErrorListener(l);
+ source.addErrorListener(new TestListener());
+ assertFalse("Unknown listener can be removed", source
+ .removeErrorListener(new TestListener()));
+ assertTrue("Could not remove listener", source.removeErrorListener(l));
+ assertFalse("Listener still in list", source.getErrorListeners()
+ .contains(l));
+ }
+
+ /**
+ * Tests if a null error listener can be removed. This should be a no-op.
+ */
+ public void testRemoveNullErrorListener()
+ {
+ source.addErrorListener(new TestListener());
+ assertFalse("Null listener can be removed", source
+ .removeErrorListener(null));
+ assertEquals("Listener list was modified", 1, source
+ .getErrorListeners().size());
+ }
+
+ /**
+ * Tests whether the listeners list is read only.
+ */
+ public void testGetErrorListenersUpdate()
+ {
+ source.addErrorListener(new TestListener());
+ Collection list = source.getErrorListeners();
+ try
+ {
+ list.add("test");
+ fail("Could manipulate list!");
+ }
+ catch (Exception ex)
+ {
+ // ok
+ }
+ }
+
+ /**
+ * Tests delivering an error event to a listener.
+ */
+ public void testFireError()
+ {
+ TestListener l = new TestListener();
+ source.addErrorListener(l);
+ Exception testException = new Exception("A test");
+ source.fireError(TEST_TYPE, TEST_PROPNAME, TEST_PROPVALUE,
+ testException);
+ assertEquals("Not 1 event created", 1, source.errorCount);
+ assertEquals("Error listener not called once", 1, l.numberOfErrors);
+ assertEquals("Normal event was generated", 0, l.numberOfCalls);
+ assertEquals("Wrong event type", TEST_TYPE, l.lastEvent.getType());
+ assertEquals("Wrong property name", TEST_PROPNAME, l.lastEvent
+ .getPropertyName());
+ assertEquals("Wrong property value", TEST_PROPVALUE, l.lastEvent
+ .getPropertyValue());
+ assertEquals("Wrong Throwable object", testException,
+ ((ConfigurationErrorEvent) l.lastEvent).getCause());
+ }
+
+ /**
+ * Tests firering an error event if there are no error listeners.
+ */
+ public void testFireErrorNoListeners()
+ {
+ source.fireError(TEST_TYPE, TEST_PROPNAME, TEST_PROPVALUE,
+ new Exception());
+ assertEquals("An error event object was created", 0, source.errorCount);
+ }
+
+ /**
+ * Tests cloning an event source object. The registered listeners should not
+ * be registered at the clone.
+ */
+ public void testClone() throws CloneNotSupportedException
+ {
+ source.addConfigurationListener(new TestListener());
+ source.addErrorListener(new TestListener());
+ EventSource copy = (EventSource) source.clone();
+ assertTrue("Configuration listeners registered for clone", copy
+ .getConfigurationListeners().isEmpty());
+ assertTrue("Error listeners registered for clone", copy
+ .getErrorListeners().isEmpty());
+ }
+
+ /**
* A test event listener implementation.
*/
- static class TestListener implements ConfigurationListener
+ static class TestListener implements ConfigurationListener,
+ ConfigurationErrorListener
{
ConfigurationEvent lastEvent;
int numberOfCalls;
+ int numberOfErrors;
+
public void configurationChanged(ConfigurationEvent event)
{
lastEvent = event;
numberOfCalls++;
}
+
+ public void configurationError(ConfigurationErrorEvent event)
+ {
+ lastEvent = event;
+ numberOfErrors++;
+ }
}
/**
* A specialized event source implementation that counts the number of
* created event objects. It is used to test whether the
* <code>fireEvent()</code> methods only creates event objects if
- * necessary.
+ * necessary. It also allows testing the clone() operation.
*/
- static class CountingEventSource extends EventSource
+ static class CountingEventSource extends EventSource implements Cloneable
{
int eventCount;
+ int errorCount;
+
protected ConfigurationEvent createEvent(int type, String propName,
Object propValue, boolean before)
{
eventCount++;
return super.createEvent(type, propName, propValue, before);
+ }
+
+ protected ConfigurationErrorEvent createErrorEvent(int type,
+ String propName, Object value, Throwable ex)
+ {
+ errorCount++;
+ return super.createErrorEvent(type, propName, value, ex);
}
}
}
---------------------------------------------------------------------
To unsubscribe, e-mail: commons-dev-unsubscribe@jakarta.apache.org
For additional commands, e-mail: commons-dev-help@jakarta.apache.org