blob: 30d3f68595f002610369c6da54626fdd22242cb0 [file] [log] [blame]
/*
* 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.configuration2.event;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
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
* @param <T> the type of events processed by this listener
* @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!");
}
for (EventListenerIterator<? extends Event> iterator =
getEventListenerIterator(event.getEventType()); iterator
.hasNext();)
{
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()
{
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);
}
/**
* Returns an (unmodifiable) list with registration information about all
* event listeners registered at this object.
*
* @return a list with event listener registration information
*/
public List<EventListenerRegistrationData<?>> getRegistrations()
{
return Collections.unmodifiableList(listeners);
}
/**
* Returns a list with {@code EventListenerRegistrationData} objects for all
* event listener registrations of the specified event type or an event type
* having this type as super type (directly or indirectly). Note that this
* is the opposite direction than querying event types for firing events: in
* this case event listener registrations are searched which are super event
* types from a given type. This method in contrast returns event listener
* registrations for listeners that extend a given super type.
*
* @param eventType the event type object
* @param <T> the event type
* @return a list with the matching event listener registration objects
*/
public <T extends Event> List<EventListenerRegistrationData<? extends T>> getRegistrationsForSuperType(
EventType<T> eventType)
{
Map<EventType<?>, Set<EventType<?>>> superTypes =
new HashMap<EventType<?>, Set<EventType<?>>>();
List<EventListenerRegistrationData<? extends T>> results =
new LinkedList<EventListenerRegistrationData<? extends T>>();
for (EventListenerRegistrationData<?> reg : listeners)
{
Set<EventType<?>> base = superTypes.get(reg.getEventType());
if (base == null)
{
base = EventType.fetchSuperEventTypes(reg.getEventType());
superTypes.put(reg.getEventType(), base);
}
if (base.contains(eventType))
{
@SuppressWarnings("unchecked")
// This is safe because we just did a check
EventListenerRegistrationData<? extends T> result =
(EventListenerRegistrationData<? extends T>) reg;
results.add(result);
}
}
return results;
}
/**
* Removes all event listeners registered at this object.
*/
public void clear()
{
listeners.clear();
}
/**
* Adds all event listener registrations stored in the specified
* {@code EventListenerList} to this list.
*
* @param c the list to be copied (must not be <b>null</b>)
* @throws IllegalArgumentException if the list to be copied is <b>null</b>
*/
public void addAll(EventListenerList c)
{
if (c == null)
{
throw new IllegalArgumentException(
"List to be copied must not be null!");
}
for (EventListenerRegistrationData<?> regData : c.getRegistrations())
{
addEventListener(regData);
}
}
/**
* 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 listener the event listener to be called
* @param event the event to be fired
*/
@SuppressWarnings("unchecked")
private static void callListener(EventListener<?> listener, Event event)
{
@SuppressWarnings("rawtypes")
EventListener rowListener = listener;
rowListener.onEvent(event);
}
/**
* 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 final 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 = EventType.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
|| !EventType.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")
private EventListener<? super T> castListener(
EventListenerRegistrationData<?> regData)
{
@SuppressWarnings("rawtypes")
EventListener listener = regData.getListener();
return listener;
}
}
}