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/05/19 21:54:44 UTC

svn commit: r1596017 - in /commons/proper/configuration/trunk/src: main/java/org/apache/commons/configuration/event/ test/java/org/apache/commons/configuration/event/

Author: oheger
Date: Mon May 19 19:54:44 2014
New Revision: 1596017

URL: http://svn.apache.org/r1596017
Log:
Added new EventListenerList class.

This is a utility class for managing event listeners. An event listener
registration consists of a listener and an event type. Events can be fired,
and the listeners registered for this type (or super types) are automatically
invoked. Listener regstrations are type-safe.

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

Added: 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=1596017&view=auto
==============================================================================
--- commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration/event/EventListenerList.java (added)
+++ commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration/event/EventListenerList.java Mon May 19 19:54:44 2014
@@ -0,0 +1,199 @@
+/*
+ * 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.configuration.event;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+/**
+ * <p>
+ * A class for managing event listeners for an event source.
+ * </p>
+ * <p>
+ * This class allows registering an arbitrary number of event listeners for
+ * specific event types. Event types are specified using the {@link EventType}
+ * class. Due to the type parameters in method signatures, it is guaranteed that
+ * registered listeners are compatible with the event types they are interested
+ * in.
+ * </p>
+ * <p>
+ * There are also methods for firing events. Here all registered listeners are
+ * determined - based on the event type specified at registration time - which
+ * should receive the event to be fired. So basically, the event type at
+ * listener registration serves as a filter criterion. Because of the
+ * hierarchical nature of event types it can be determined in a fine-grained way
+ * which events are propagated to which listeners. It is also possible to
+ * register a listener multiple times for different event types.
+ * </p>
+ * <p>
+ * Implementation note: This class is thread-safe.
+ * </p>
+ *
+ * @version $Id$
+ * @since 2.0
+ */
+public class EventListenerList
+{
+    /** A list with the listeners added to this object. */
+    private final List<EventListenerRegistrationData<?>> listeners;
+
+    /**
+     * Creates a new instance of {@code EventListenerList}.
+     */
+    public EventListenerList()
+    {
+        listeners =
+                new CopyOnWriteArrayList<EventListenerRegistrationData<?>>();
+    }
+
+    /**
+     * Adds an event listener for the specified event type. This listener is
+     * notified about events of this type and all its sub types.
+     *
+     * @param type the event type (must not be <b>null</b>)
+     * @param listener the listener to be registered (must not be <b>null</b>)
+     * @param <T> the type of events processed by this listener
+     * @throws IllegalArgumentException if a required parameter is <b>null</b>
+     */
+    public <T extends Event> void addEventListener(EventType<T> type,
+            EventListener<? super T> listener)
+    {
+        listeners.add(new EventListenerRegistrationData<T>(type, listener));
+    }
+
+    /**
+     * Adds the specified listener registration data object to the internal list
+     * of event listeners. This is an alternative registration method; the event
+     * type and the listener are passed as a single data object.
+     *
+     * @param regData the registration data object (must not be <b>null</b>)
+     * @param <T> the type of events processed by this listener
+     * @throws IllegalArgumentException if the registration data object is
+     *         <b>null</b>
+     */
+    public <T extends Event> void addEventListener(
+            EventListenerRegistrationData<T> regData)
+    {
+        if (regData == null)
+        {
+            throw new IllegalArgumentException(
+                    "EventListenerRegistrationData must not be null!");
+        }
+        listeners.add(regData);
+    }
+
+    /**
+     * Removes the event listener registration for the given event type and
+     * listener. An event listener instance may be registered multiple times for
+     * different event types. Therefore, when removing a listener the event type
+     * of the registration in question has to be specified. The return value
+     * indicates whether a registration was removed. A value of <b>false</b>
+     * means that no such combination of event type and listener was found.
+     *
+     * @param eventType the event type
+     * @param listener the event listener to be removed
+     * @return a flag whether a listener registration was removed
+     */
+    public <T extends Event> boolean removeEventListener(
+            EventType<T> eventType, EventListener<? super T> listener)
+    {
+        return !(listener == null || eventType == null)
+                && removeEventListener(new EventListenerRegistrationData<T>(
+                        eventType, listener));
+    }
+
+    /**
+     * Removes the event listener registration defined by the passed in data
+     * object. This is an alternative method for removing a listener which
+     * expects the event type and the listener in a single data object.
+     *
+     * @param regData the registration data object
+     * @param <T> the type of events processed by this listener
+     * @return a flag whether a listener registration was removed
+     * @see #removeEventListener(EventType, EventListener)
+     */
+    public <T extends Event> boolean removeEventListener(
+            EventListenerRegistrationData<T> regData)
+    {
+        return listeners.remove(regData);
+    }
+
+    /**
+     * Fires an event to all registered listeners matching the event type.
+     *
+     * @param event the event to be fired (must not be <b>null</b>)
+     * @throws IllegalArgumentException if the event is <b>null</b>
+     */
+    public void fire(Event event)
+    {
+        if (event == null)
+        {
+            throw new IllegalArgumentException(
+                    "Event to be fired must not be null!");
+        }
+        Set<EventType<?>> matchingTypes =
+                fetchSuperEventTypes(event.getEventType());
+
+        for (EventListenerRegistrationData<?> listenerData : listeners)
+        {
+            if (matchingTypes.contains(listenerData.getEventType()))
+            {
+                callListener(listenerData, event);
+            }
+        }
+    }
+
+    /**
+     * 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.
+     *
+     * @param listenerData the event listener data
+     * @param event the event to be fired
+     */
+    @SuppressWarnings("unchecked, rawtypes")
+    private static void callListener(
+            EventListenerRegistrationData<?> listenerData, Event event)
+    {
+        EventListener listener = listenerData.getListener();
+        listener.onEvent(event);
+    }
+
+    /**
+     * Obtains a set of all super event types for the specified type (including
+     * the type itself). If an event listener was registered for one of these
+     * types, it can handle an event of the specified type.
+     *
+     * @param eventType the event type in question
+     * @return the set with all super event types
+     */
+    private static Set<EventType<?>> fetchSuperEventTypes(EventType<?> eventType)
+    {
+        Set<EventType<?>> types = new HashSet<EventType<?>>();
+        EventType<?> currentType = eventType;
+        while (currentType != null)
+        {
+            types.add(currentType);
+            currentType = currentType.getSuperType();
+        }
+        return types;
+    }
+}

Added: commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration/event/EventListenerRegistrationData.java
URL: http://svn.apache.org/viewvc/commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration/event/EventListenerRegistrationData.java?rev=1596017&view=auto
==============================================================================
--- commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration/event/EventListenerRegistrationData.java (added)
+++ commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration/event/EventListenerRegistrationData.java Mon May 19 19:54:44 2014
@@ -0,0 +1,123 @@
+/*
+ * 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.configuration.event;
+
+/**
+ * <p>
+ * A data class holding information about an event listener registration.
+ * </p>
+ * <p>
+ * An instance of this class stores all information required to determine
+ * whether a specific event listener is to be invoked for a given event. The
+ * class is used internally by {@link EventListenerList}, but is also useful in
+ * general when information about event listeners is to be stored.
+ * </p>
+ * <p>
+ * Implementation note: Instances of this class are immutable and can safely be
+ * shared between multiple threads or components.
+ * </p>
+ *
+ * @version $Id$
+ * @since 2.0
+ * @param <T> the type of events processed by the listener
+ */
+public final class EventListenerRegistrationData<T extends Event>
+{
+    /** The event type. */
+    private final EventType<T> eventType;
+
+    /** The event listener. */
+    private final EventListener<? super T> listener;
+
+    /**
+     * Creates a new instance of {@code EventListenerRegistrationData}.
+     *
+     * @param type the event type (must not be <b>null</b>)
+     * @param lstnr the event listener (must not be <b>null</b>)
+     * @throws IllegalArgumentException if a required parameter is <b>null</b>
+     */
+    public EventListenerRegistrationData(EventType<T> type,
+            EventListener<? super T> lstnr)
+    {
+        if (type == null)
+        {
+            throw new IllegalArgumentException("Event type must not be null!");
+        }
+        if (lstnr == null)
+        {
+            throw new IllegalArgumentException(
+                    "Listener to be registered must not be null!");
+        }
+
+        eventType = type;
+        listener = lstnr;
+    }
+
+    /**
+     * Returns the event type for this listener registration.
+     *
+     * @return the event type
+     */
+    public EventType<T> getEventType()
+    {
+        return eventType;
+    }
+
+    /**
+     * Returns the listener this registration is about.
+     *
+     * @return the event listener
+     */
+    public EventListener<? super T> getListener()
+    {
+        return listener;
+    }
+
+    @Override
+    public int hashCode()
+    {
+        int result = eventType.hashCode();
+        result = 31 * result + listener.hashCode();
+        return result;
+    }
+
+    /**
+     * Compares this object with another one. Two instances of
+     * {@code EventListenerRegistrationData} are considered equal if they
+     * reference the same listener and event type.
+     *
+     * @param obj the object to be compared to
+     * @return a flag whether these objects are equal
+     */
+    @Override
+    public boolean equals(Object obj)
+    {
+        if (this == obj)
+        {
+            return true;
+        }
+        if (!(obj instanceof EventListenerRegistrationData))
+        {
+            return false;
+        }
+
+        EventListenerRegistrationData<?> c =
+                (EventListenerRegistrationData<?>) obj;
+        return getListener() == c.getListener()
+                && getEventType().equals(c.getEventType());
+    }
+}

Added: 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=1596017&view=auto
==============================================================================
--- commons/proper/configuration/trunk/src/test/java/org/apache/commons/configuration/event/TestEventListenerList.java (added)
+++ commons/proper/configuration/trunk/src/test/java/org/apache/commons/configuration/event/TestEventListenerList.java Mon May 19 19:54:44 2014
@@ -0,0 +1,366 @@
+/*
+ * 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.configuration.event;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+/**
+ * Test class for {@code EventListenerList.}
+ *
+ * @since 2.0
+ */
+public class TestEventListenerList
+{
+    /** Constant for a test event message. */
+    private static final String MESSAGE = "TestEventMessage";
+
+    /** Type for the base event. */
+    private static EventType<EventBase> typeBase;
+
+    /** Type for sub event 1. */
+    private static EventType<EventSub1> typeSub1;
+
+    /** Type for sub event 2. */
+    private static EventType<EventSub2> typeSub2;
+
+    /** The list to be tested. */
+    private EventListenerList list;
+
+    @BeforeClass
+    public static void setUpBeforeClass() throws Exception
+    {
+        typeBase = new EventType<EventBase>(Event.ANY, "BASE");
+        typeSub1 = new EventType<EventSub1>(typeBase, "SUB1");
+        typeSub2 = new EventType<EventSub2>(typeBase, "SUB2");
+    }
+
+    @Before
+    public void setUp() throws Exception
+    {
+        list = new EventListenerList();
+    }
+
+    /**
+     * Tries to register a listener for a null event type.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testRegisterEventTypeNull()
+    {
+        list.addEventListener(null, new ListenerTestImpl());
+    }
+
+    /**
+     * Tests that null event listeners cannot be registered.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testRegisterListenerNull()
+    {
+        list.addEventListener(typeBase, null);
+    }
+
+    /**
+     * Tests that a null event is rejected by fire().
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testFireNullEvent()
+    {
+        list.fire(null);
+    }
+
+    /**
+     * Tests whether events matching the registration type are delivered.
+     */
+    @Test
+    public void testReceiveEventOfExactType()
+    {
+        ListenerTestImpl listener = new ListenerTestImpl();
+        list.addEventListener(typeSub1, listener);
+
+        list.fire(new EventSub1(this, typeSub1, MESSAGE));
+        listener.assertEvent(this, typeSub1, MESSAGE);
+    }
+
+    /**
+     * Tests whether multiple event listeners can be registered.
+     */
+    @Test
+    public void testReceiveEventMultipleListeners()
+    {
+        ListenerTestImpl listener1 = new ListenerTestImpl();
+        ListenerTestImpl listener2 = new ListenerTestImpl();
+        list.addEventListener(typeSub1, listener1);
+        list.addEventListener(typeSub1, listener2);
+
+        list.fire(new EventSub1(this, typeSub1, MESSAGE));
+        listener1.assertEvent(this, typeSub1, MESSAGE);
+        listener2.assertEvent(this, typeSub1, MESSAGE);
+    }
+
+    /**
+     * Tests whether the event type is taken into account when calling
+     * listeners.
+     */
+    @Test
+    public void testReceiveEventDifferentType()
+    {
+        ListenerTestImpl listener1 = new ListenerTestImpl();
+        ListenerTestImpl listener2 = new ListenerTestImpl();
+        list.addEventListener(typeSub1, listener1);
+        list.addEventListener(typeSub2, listener2);
+
+        list.fire(new EventSub1(this, typeSub1, MESSAGE));
+        listener1.assertEvent(this, typeSub1, MESSAGE);
+        listener2.assertNoEvent();
+    }
+
+    /**
+     * Tests that events of a base type do not cause a listener to be invoked.
+     */
+    @Test
+    public void testSuppressEventOfSuperType()
+    {
+        ListenerTestImpl listener = new ListenerTestImpl();
+        list.addEventListener(typeSub1, listener);
+
+        list.fire(new EventBase(this, typeBase, MESSAGE));
+        listener.assertNoEvent();
+    }
+
+    /**
+     * Tests that events of a derived type are delivered to listeners registered
+     * for a base type.
+     */
+    @Test
+    public void testReceiveEventSubType()
+    {
+        ListenerTestImpl listener = new ListenerTestImpl();
+        list.addEventListener(typeBase, listener);
+
+        list.fire(new EventSub1(this, typeSub1, MESSAGE));
+        listener.assertEvent(this, typeSub1, MESSAGE);
+    }
+
+    /**
+     * Tests whether an event listener can be registered via a registration data
+     * object.
+     */
+    @Test
+    public void testListenerRegistrationWithListenerData()
+    {
+        ListenerTestImpl listener = new ListenerTestImpl();
+        EventListenerRegistrationData<EventSub1> regData =
+                new EventListenerRegistrationData<EventSub1>(typeSub1, listener);
+        list.addEventListener(regData);
+
+        list.fire(new EventSub1(this, typeSub1, MESSAGE));
+        listener.assertEvent(this, typeSub1, MESSAGE);
+    }
+
+    /**
+     * Tries to register a listener with a null registration data object.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testListenerRegistrationWithNullListenerData()
+    {
+        list.addEventListener(null);
+    }
+
+    /**
+     * Tests removeEventListener() for a non-existing event listener.
+     */
+    @Test
+    public void testRemoveEventListenerNonExistingListener()
+    {
+        list.addEventListener(typeBase, new ListenerTestImpl());
+        assertFalse("Wrong result",
+                list.removeEventListener(typeBase, new ListenerTestImpl()));
+    }
+
+    /**
+     * Tests removeEventListener() if another event type is specified for an
+     * existing listener.
+     */
+    @Test
+    public void testRemoveEventListenerNonExistingEventType()
+    {
+        ListenerTestImpl listener = new ListenerTestImpl();
+        list.addEventListener(typeSub1, listener);
+
+        assertFalse("Wrong result",
+                list.removeEventListener(typeBase, listener));
+    }
+
+    /**
+     * Tests whether an event listener can be removed.
+     */
+    @Test
+    public void testRemoveEventListenerExisting()
+    {
+        ListenerTestImpl listener = new ListenerTestImpl();
+        list.addEventListener(typeSub1, listener);
+
+        assertTrue("Wrong result", list.removeEventListener(typeSub1, listener));
+        list.fire(new EventSub1(this, typeSub1, MESSAGE));
+        listener.assertNoEvent();
+    }
+
+    /**
+     * Tests that removeEventListener() can handle a null registration object.
+     */
+    @Test
+    public void testRemoveEventListenerNullRegistration()
+    {
+        assertFalse("Wrong result", list.removeEventListener(null));
+    }
+
+    /**
+     * Tests that removeEventListener() can handle a null listener.
+     */
+    @Test
+    public void testRemoveEventListenerNullListener()
+    {
+        assertFalse("Wrong result", list.removeEventListener(typeBase, null));
+    }
+
+    /**
+     * Tests that removeEventListener() can handle a null event type.
+     */
+    @Test
+    public void testRemoveEventListenerNullType()
+    {
+        assertFalse("Wrong result",
+                list.removeEventListener(null, new ListenerTestImpl()));
+    }
+
+    /**
+     * Tests that a listener can be registered multiple times for different
+     * event types.
+     */
+    @Test
+    public void testMultipleListenerRegistration()
+    {
+        ListenerTestImpl listener = new ListenerTestImpl();
+        list.addEventListener(typeSub1, listener);
+        list.addEventListener(typeSub2, listener);
+
+        list.fire(new EventSub2(this, typeSub2, MESSAGE));
+        list.removeEventListener(typeSub1, listener);
+        list.fire(new EventSub1(this, typeSub1, MESSAGE));
+        listener.assertEvent(this, typeSub2, MESSAGE);
+    }
+
+    /**
+     * 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.
+     */
+    private static class EventBase extends Event
+    {
+        /** An event message for testing pay-load. */
+        private final String message;
+
+        public EventBase(Object source, EventType<? extends EventBase> type,
+                String msg)
+        {
+            super(source, type);
+            message = msg;
+        }
+
+        public String getMessage()
+        {
+            return message;
+        }
+    }
+
+    /**
+     * A test event class derived from the base test event class.
+     */
+    private static class EventSub1 extends EventBase
+    {
+        public EventSub1(Object source, EventType<? extends EventSub1> type,
+                String msg)
+        {
+            super(source, type, msg);
+        }
+    }
+
+    /**
+     * Another test event class derived from the base class.
+     */
+    private static class EventSub2 extends EventBase
+    {
+        public EventSub2(Object source, EventType<? extends EventSub2> type,
+                String msg)
+        {
+            super(source, type, msg);
+        }
+    }
+
+    /**
+     * A test event listener implementation. This listener class expects that it
+     * receives at most a single event. This event is stored for further
+     * evaluation.
+     */
+    private static class ListenerTestImpl implements EventListener<EventBase>
+    {
+        /** The event received by this object. */
+        private EventBase receivedEvent;
+
+        @Override
+        public void onEvent(EventBase event)
+        {
+            assertNull("Too many events: " + event, receivedEvent);
+            receivedEvent = event;
+        }
+
+        /**
+         * Checks that this listener has not received any event.
+         */
+        public void assertNoEvent()
+        {
+            assertNull("Unexpected event received: " + receivedEvent,
+                    receivedEvent);
+        }
+
+        /**
+         * Checks that this listener has received an event with the expected
+         * properties.
+         *
+         * @param expSource the expected source
+         * @param expType the expected type
+         * @param expMessage the expected message
+         */
+        public void assertEvent(Object expSource, EventType<?> expType,
+                String expMessage)
+        {
+            assertNotNull("No event received", receivedEvent);
+            assertEquals("Wrong source", expSource, receivedEvent.getSource());
+            assertEquals("Wrong event type", expType,
+                    receivedEvent.getEventType());
+            assertEquals("Wrong message", expMessage,
+                    receivedEvent.getMessage());
+        }
+    }
+}