EventListenerList.java
- /*
- * 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>
- *
- * @since 2.0
- */
- public class EventListenerList {
- /**
- * 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(final Iterator<EventListenerRegistrationData<?>> it, final EventType<T> base) {
- underlyingIterator = it;
- baseEventType = base;
- acceptedTypes = EventType.fetchSuperEventTypes(base);
- initNextElement();
- }
- /**
- * 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(final EventListenerRegistrationData<?> regData) {
- @SuppressWarnings("rawtypes")
- final EventListener listener = regData.getListener();
- return listener;
- }
- @Override
- public boolean hasNext() {
- return nextElement != null;
- }
- /**
- * Determines the next element in the iteration.
- */
- private void initNextElement() {
- nextElement = null;
- while (underlyingIterator.hasNext() && nextElement == null) {
- final EventListenerRegistrationData<?> regData = underlyingIterator.next();
- if (acceptedTypes.contains(regData.getEventType())) {
- nextElement = castListener(regData);
- }
- }
- }
- /**
- * 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(final Event event) {
- validateEvent(event);
- invokeNextListenerUnchecked(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(final Event event) {
- callListener(next(), event);
- }
- @Override
- public EventListener<? super T> next() {
- if (nextElement == null) {
- throw new NoSuchElementException("No more event listeners!");
- }
- final EventListener<? super T> result = nextElement;
- initNextElement();
- return result;
- }
- /**
- * {@inheritDoc} This implementation always throws an exception. Removing elements is not supported.
- */
- @Override
- public void remove() {
- throw new UnsupportedOperationException("Removing elements is not supported!");
- }
- /**
- * 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(final Event event) {
- if (event == null || !EventType.fetchSuperEventTypes(event.getEventType()).contains(baseEventType)) {
- throw new IllegalArgumentException("Event incompatible with listener iteration: " + event);
- }
- }
- }
- /**
- * 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(final EventListener<?> listener, final Event event) {
- @SuppressWarnings("rawtypes")
- final EventListener rowListener = listener;
- rowListener.onEvent(event);
- }
- /** 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<>();
- }
- /**
- * Adds all event listener registrations stored in the specified {@code EventListenerList} to this list.
- *
- * @param c the list to be copied (must not be <strong>null</strong>)
- * @throws IllegalArgumentException if the list to be copied is <strong>null</strong>
- */
- public void addAll(final EventListenerList c) {
- if (c == null) {
- throw new IllegalArgumentException("List to be copied must not be null!");
- }
- c.getRegistrations().forEach(this::addEventListener);
- }
- /**
- * 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 <strong>null</strong>)
- * @param <T> the type of events processed by this listener
- * @throws IllegalArgumentException if the registration data object is <strong>null</strong>
- */
- public <T extends Event> void addEventListener(final EventListenerRegistrationData<T> regData) {
- if (regData == null) {
- throw new IllegalArgumentException("EventListenerRegistrationData must not be null!");
- }
- listeners.add(regData);
- }
- /**
- * 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 <strong>null</strong>)
- * @param listener the listener to be registered (must not be <strong>null</strong>)
- * @param <T> the type of events processed by this listener
- * @throws IllegalArgumentException if a required parameter is <strong>null</strong>
- */
- public <T extends Event> void addEventListener(final EventType<T> type, final EventListener<? super T> listener) {
- listeners.add(new EventListenerRegistrationData<>(type, listener));
- }
- /**
- * Removes all event listeners registered at this object.
- */
- public void clear() {
- listeners.clear();
- }
- /**
- * Fires an event to all registered listeners matching the event type.
- *
- * @param event the event to be fired (must not be <strong>null</strong>)
- * @throws IllegalArgumentException if the event is <strong>null</strong>
- */
- public void fire(final Event event) {
- if (event == null) {
- throw new IllegalArgumentException("Event to be fired must not be null!");
- }
- for (final EventListenerIterator<? extends Event> iterator = getEventListenerIterator(event.getEventType()); iterator.hasNext();) {
- iterator.invokeNextListenerUnchecked(event);
- }
- }
- /**
- * Gets 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(final EventType<T> eventType) {
- return new EventListenerIterator<>(listeners.iterator(), eventType);
- }
- /**
- * Gets 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 () -> getEventListenerIterator(eventType);
- }
- /**
- * Gets 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);
- }
- /**
- * Gets 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(final EventType<T> eventType) {
- final Map<EventType<?>, Set<EventType<?>>> superTypes = new HashMap<>();
- final List<EventListenerRegistrationData<? extends T>> results = new LinkedList<>();
- listeners.forEach(reg -> {
- final Set<EventType<?>> base = superTypes.computeIfAbsent(reg.getEventType(), EventType::fetchSuperEventTypes);
- if (base.contains(eventType)) {
- @SuppressWarnings("unchecked")
- final
- // This is safe because we just did a check
- EventListenerRegistrationData<? extends T> result = (EventListenerRegistrationData<? extends T>) reg;
- results.add(result);
- }
- });
- return results;
- }
- /**
- * 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(final EventListenerRegistrationData<T> regData) {
- return listeners.remove(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 <strong>false</strong> 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(final EventType<T> eventType, final EventListener<? super T> listener) {
- return !(listener == null || eventType == null) && removeEventListener(new EventListenerRegistrationData<>(eventType, listener));
- }
- }