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>[EMAIL PROTECTED] 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>[EMAIL PROTECTED] 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: [EMAIL PROTECTED] For additional commands, e-mail: [EMAIL PROTECTED]