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());
+ }
+ }
+}