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 2014/07/11 22:15:08 UTC

svn commit: r1609789 - in /commons/proper/configuration/trunk/src: main/java/org/apache/commons/configuration/event/EventListenerList.java test/java/org/apache/commons/configuration/event/TestEventListenerList.java

Author: oheger
Date: Fri Jul 11 20:15:08 2014
New Revision: 1609789

URL: http://svn.apache.org/r1609789
Log:
Added methods to EventListenerList for querying registered event listeners.

This is mainly useful in unit tests, but also supports advanced iteration over
event listeners.

Modified:
    commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration/event/EventListenerList.java
    commons/proper/configuration/trunk/src/test/java/org/apache/commons/configuration/event/TestEventListenerList.java

Modified: commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration/event/EventListenerList.java
URL: http://svn.apache.org/viewvc/commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration/event/EventListenerList.java?rev=1609789&r1=1609788&r2=1609789&view=diff
==============================================================================
--- commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration/event/EventListenerList.java (original)
+++ commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration/event/EventListenerList.java Fri Jul 11 20:15:08 2014
@@ -17,7 +17,9 @@
 package org.apache.commons.configuration.event;
 
 import java.util.HashSet;
+import java.util.Iterator;
 import java.util.List;
+import java.util.NoSuchElementException;
 import java.util.Set;
 import java.util.concurrent.CopyOnWriteArrayList;
 
@@ -147,34 +149,65 @@ public class EventListenerList
             throw new IllegalArgumentException(
                     "Event to be fired must not be null!");
         }
-        Set<EventType<?>> matchingTypes =
-                fetchSuperEventTypes(event.getEventType());
 
-        for (EventListenerRegistrationData<?> listenerData : listeners)
+        for (EventListenerIterator<? extends Event> iterator =
+                getEventListenerIterator(event.getEventType()); iterator
+                .hasNext();)
         {
-            if (matchingTypes.contains(listenerData.getEventType()))
+            iterator.invokeNextListenerUnchecked(event);
+        }
+    }
+
+    /**
+     * Returns an {@code Iterable} allowing access to all event listeners stored
+     * in this list which are compatible with the specified event type.
+     *
+     * @param eventType the event type object
+     * @param <T> the event type
+     * @return an {@code Iterable} with the selected event listeners
+     */
+    public <T extends Event> Iterable<EventListener<? super T>> getEventListeners(
+            final EventType<T> eventType)
+    {
+        return new Iterable<EventListener<? super T>>()
+        {
+            @Override
+            public Iterator<EventListener<? super T>> iterator()
             {
-                callListener(listenerData, event);
+                return getEventListenerIterator(eventType);
             }
-        }
+        };
+    }
+
+    /**
+     * Returns a specialized iterator for obtaining all event listeners stored
+     * in this list which are compatible with the specified event type.
+     *
+     * @param eventType the event type object
+     * @param <T> the event type
+     * @return an {@code Iterator} with the selected event listeners
+     */
+    public <T extends Event> EventListenerIterator<T> getEventListenerIterator(
+            EventType<T> eventType)
+    {
+        return new EventListenerIterator<T>(listeners.iterator(), eventType);
     }
 
     /**
-     * Helper method for calling an event listener from a listener registration
-     * data. We have to operate on raw types to make this code compile. However,
-     * this is safe because of the way the listeners have been registered and
-     * associated with event types - so it is ensured that the event is
-     * compatible with the listener.
+     * Helper method for calling an event listener with an event. We have to
+     * operate on raw types to make this code compile. However, this is safe
+     * because of the way the listeners have been registered and associated with
+     * event types - so it is ensured that the event is compatible with the
+     * listener.
      *
-     * @param listenerData the event listener data
+     * @param listener the event listener to be called
      * @param event the event to be fired
      */
     @SuppressWarnings("unchecked, rawtypes")
-    private static void callListener(
-            EventListenerRegistrationData<?> listenerData, Event event)
+    private static void callListener(EventListener<?> listener, Event event)
     {
-        EventListener listener = listenerData.getListener();
-        listener.onEvent(event);
+        EventListener rowListener = listener;
+        rowListener.onEvent(event);
     }
 
     /**
@@ -196,4 +229,145 @@ public class EventListenerList
         }
         return types;
     }
+
+    /**
+     * A special {@code Iterator} implementation used by the
+     * {@code getEventListenerIterator()} method. This iterator returns only
+     * listeners compatible with a specified event type. It has a convenience
+     * method for invoking the current listener in the iteration with an event.
+     *
+     * @param <T> the event type
+     */
+    public static class EventListenerIterator<T extends Event> implements
+            Iterator<EventListener<? super T>>
+    {
+        /** The underlying iterator. */
+        private final Iterator<EventListenerRegistrationData<?>> underlyingIterator;
+
+        /** The base event type. */
+        private final EventType<T> baseEventType;
+
+        /** The set with accepted event types. */
+        private final Set<EventType<?>> acceptedTypes;
+
+        /** The next element in the iteration. */
+        private EventListener<? super T> nextElement;
+
+        private EventListenerIterator(
+                Iterator<EventListenerRegistrationData<?>> it, EventType<T> base)
+        {
+            underlyingIterator = it;
+            baseEventType = base;
+            acceptedTypes = fetchSuperEventTypes(base);
+            initNextElement();
+        }
+
+        @Override
+        public boolean hasNext()
+        {
+            return nextElement != null;
+        }
+
+        @Override
+        public EventListener<? super T> next()
+        {
+            if (nextElement == null)
+            {
+                throw new NoSuchElementException("No more event listeners!");
+            }
+
+            EventListener<? super T> result = nextElement;
+            initNextElement();
+            return result;
+        }
+
+        /**
+         * Obtains the next event listener in this iteration and invokes it with
+         * the given event object.
+         *
+         * @param event the event object
+         * @throws NoSuchElementException if iteration is at its end
+         */
+        public void invokeNext(Event event)
+        {
+            validateEvent(event);
+            invokeNextListenerUnchecked(event);
+        }
+
+        /**
+         * {@inheritDoc} This implementation always throws an exception.
+         * Removing elements is not supported.
+         */
+        @Override
+        public void remove()
+        {
+            throw new UnsupportedOperationException(
+                    "Removing elements is not supported!");
+        }
+
+        /**
+         * Determines the next element in the iteration.
+         */
+        private void initNextElement()
+        {
+            nextElement = null;
+            while (underlyingIterator.hasNext() && nextElement == null)
+            {
+                EventListenerRegistrationData<?> regData =
+                        underlyingIterator.next();
+                if (acceptedTypes.contains(regData.getEventType()))
+                {
+                    nextElement = castListener(regData);
+                }
+            }
+        }
+
+        /**
+         * Checks whether the specified event can be passed to an event listener
+         * in this iteration. This check is done via the hierarchy of event
+         * types.
+         *
+         * @param event the event object
+         * @throws IllegalArgumentException if the event is invalid
+         */
+        private void validateEvent(Event event)
+        {
+            if (event == null
+                    || !fetchSuperEventTypes(event.getEventType()).contains(
+                            baseEventType))
+            {
+                throw new IllegalArgumentException(
+                        "Event incompatible with listener iteration: " + event);
+            }
+        }
+
+        /**
+         * Invokes the next event listener in the iteration without doing a
+         * validity check on the event. This method is called internally to
+         * avoid duplicate event checks.
+         *
+         * @param event the event object
+         */
+        private void invokeNextListenerUnchecked(Event event)
+        {
+            EventListener<? super T> listener = next();
+            callListener(listener, event);
+        }
+
+        /**
+         * Extracts the listener from the given data object and performs a cast
+         * to the target type. This is safe because it has been checked before
+         * that the type is compatible.
+         *
+         * @param regData the data object
+         * @return the extracted listener
+         */
+        @SuppressWarnings("unchecked, rawtypes")
+        private EventListener<? super T> castListener(
+                EventListenerRegistrationData<?> regData)
+        {
+            EventListener listener = regData.getListener();
+            return listener;
+        }
+    }
 }

Modified: commons/proper/configuration/trunk/src/test/java/org/apache/commons/configuration/event/TestEventListenerList.java
URL: http://svn.apache.org/viewvc/commons/proper/configuration/trunk/src/test/java/org/apache/commons/configuration/event/TestEventListenerList.java?rev=1609789&r1=1609788&r2=1609789&view=diff
==============================================================================
--- commons/proper/configuration/trunk/src/test/java/org/apache/commons/configuration/event/TestEventListenerList.java (original)
+++ commons/proper/configuration/trunk/src/test/java/org/apache/commons/configuration/event/TestEventListenerList.java Fri Jul 11 20:15:08 2014
@@ -22,6 +22,12 @@ import static org.junit.Assert.assertNot
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.NoSuchElementException;
+
 import org.junit.Before;
 import org.junit.BeforeClass;
 import org.junit.Test;
@@ -272,6 +278,156 @@ public class TestEventListenerList
     }
 
     /**
+     * Helper method for collecting the event listeners in the given iterable.
+     *
+     * @param iterable the iterable
+     * @return a list with the content of the iterable
+     */
+    private static <T extends Event> List<EventListener<? super T>> fetchListeners(
+            Iterable<EventListener<? super T>> iterable)
+    {
+        List<EventListener<? super T>> listeners =
+                new LinkedList<EventListener<? super T>>();
+        for (EventListener<? super T> listener : iterable)
+        {
+            listeners.add(listener);
+        }
+        return listeners;
+    }
+
+    /**
+     * Helper method for checking whether a specific set of event listeners is
+     * returned by getEventListeners().
+     *
+     * @param eventType the event type
+     * @param expListeners the expected listeners
+     */
+    private void checkEventListenersForType(
+            EventType<? extends Event> eventType,
+            EventListener<?>... expListeners)
+    {
+        List<?> listeners = fetchListeners(list.getEventListeners(eventType));
+        assertEquals("Wrong number of listeners", expListeners.length,
+                listeners.size());
+        assertTrue("Wrong event listeners: " + listeners,
+                listeners.containsAll(Arrays.asList(expListeners)));
+    }
+
+    /**
+     * Tests whether event listeners for a null type can be queried.
+     */
+    @Test
+    public void testGetEventListenersNull()
+    {
+        assertTrue("Got listeners",
+                fetchListeners(list.getEventListeners(null)).isEmpty());
+    }
+
+    /**
+     * Tests that an empty result is correctly handled by getEventListeners().
+     */
+    @Test
+    public void testGetEventListenersNoMatch()
+    {
+        list.addEventListener(typeSub1, new ListenerTestImpl());
+        checkEventListenersForType(typeSub2);
+    }
+
+    /**
+     * Tests whether only matching event listeners are returned by
+     * getEventListeners().
+     */
+    @Test
+    public void testGetEventListenersMatchingType()
+    {
+        ListenerTestImpl listener1 = new ListenerTestImpl();
+        ListenerTestImpl listener2 = new ListenerTestImpl();
+        list.addEventListener(typeSub1, listener1);
+        list.addEventListener(typeSub2, listener2);
+        checkEventListenersForType(typeSub1, listener1);
+    }
+
+    /**
+     * Tests whether the base type is taken into account when querying for event
+     * listeners.
+     */
+    @Test
+    public void testGetEventListenersBaseType()
+    {
+        ListenerTestImpl listener1 = new ListenerTestImpl();
+        ListenerTestImpl listener2 = new ListenerTestImpl();
+        list.addEventListener(typeBase, listener1);
+        list.addEventListener(typeBase, listener2);
+        checkEventListenersForType(typeSub1, listener1, listener2);
+    }
+
+    /**
+     * Tests that the iterator returned by getEventListeners() throws an
+     * exception if the iteration goes beyond the last element.
+     */
+    @Test(expected = NoSuchElementException.class)
+    public void testGetEventListenersIteratorNextNoElement()
+    {
+        ListenerTestImpl listener1 = new ListenerTestImpl();
+        ListenerTestImpl listener2 = new ListenerTestImpl();
+        list.addEventListener(typeBase, listener1);
+        list.addEventListener(typeBase, listener2);
+        Iterator<EventListener<? super EventBase>> iterator =
+                list.getEventListeners(typeBase).iterator();
+        for (int i = 0; i < 3; i++)
+        {
+            iterator.next();
+        }
+    }
+
+    /**
+     * Tests that the iterator returned by getEventListeners() does not support
+     * remove() operations.
+     */
+    @Test(expected = UnsupportedOperationException.class)
+    public void testGetEventListenersIteratorRemove()
+    {
+        list.addEventListener(typeBase, new ListenerTestImpl());
+        Iterator<EventListener<? super EventBase>> iterator =
+                list.getEventListeners(typeBase).iterator();
+        assertTrue("Wrong result", iterator.hasNext());
+        iterator.remove();
+    }
+
+    /**
+     * Tests whether the event listener iterator validates the passed in event
+     * object.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testEventListenerIteratorWrongEvent()
+    {
+        EventListener<EventSub2> listener = new EventListener<EventSub2>()
+        {
+            @Override
+            public void onEvent(EventSub2 event)
+            {
+            }
+        };
+        list.addEventListener(typeSub2, listener);
+        EventListenerList.EventListenerIterator<EventSub2> iterator =
+                list.getEventListenerIterator(typeSub2);
+        assertTrue("No elements", iterator.hasNext());
+        iterator.invokeNext(new EventBase(this, typeBase, "Test"));
+    }
+
+    /**
+     * Tests that a null event is handled by the iterator.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testEventListenerIteratorNullEvent()
+    {
+        list.addEventListener(typeBase, new ListenerTestImpl());
+        EventListenerList.EventListenerIterator<EventBase> iterator =
+                list.getEventListenerIterator(typeBase);
+        iterator.invokeNext(null);
+    }
+
+    /**
      * Test event class. For testing purposes, a small hierarchy of test event
      * class is created. This way it can be checked whether event types are
      * correctly evaluated and take the event hierarchy into account.