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 &quot;normal&quot; 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