    001/*
    002 * Licensed to the Apache Software Foundation (ASF) under one or more
    003 * contributor license agreements.  See the NOTICE file distributed with
    004 * this work for additional information regarding copyright ownership.
    005 * The ASF licenses this file to You under the Apache License, Version 2.0
    006 * (the "License"); you may not use this file except in compliance with
    007 * the License.  You may obtain a copy of the License at
    008 *
    009 *     http://www.apache.org/licenses/LICENSE-2.0
    010 *
    011 * Unless required by applicable law or agreed to in writing, software
    012 * distributed under the License is distributed on an "AS IS" BASIS,
    013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014 * See the License for the specific language governing permissions and
    015 * limitations under the License.
    016 */
    017package org.apache.commons.configuration2.event;
    018
    019import java.util.Collections;
    020import java.util.HashMap;
    021import java.util.Iterator;
    022import java.util.LinkedList;
    023import java.util.List;
    024import java.util.Map;
    025import java.util.NoSuchElementException;
    026import java.util.Set;
    027import java.util.concurrent.CopyOnWriteArrayList;
    028
    029/**
    030 * <p>
    031 * A class for managing event listeners for an event source.
    032 * </p>
    033 * <p>
    034 * This class allows registering an arbitrary number of event listeners for specific event types. Event types are
    035 * specified using the {@link EventType} class. Due to the type parameters in method signatures, it is guaranteed that
    036 * registered listeners are compatible with the event types they are interested in.
    037 * </p>
    038 * <p>
    039 * There are also methods for firing events. Here all registered listeners are determined - based on the event type
    040 * specified at registration time - which should receive the event to be fired. So basically, the event type at listener
    041 * registration serves as a filter criterion. Because of the hierarchical nature of event types it can be determined in
    042 * a fine-grained way which events are propagated to which listeners. It is also possible to register a listener
    043 * multiple times for different event types.
    044 * </p>
    045 * <p>
    046 * Implementation note: This class is thread-safe.
    047 * </p>
    048 *
    049 * @since 2.0
    050 */
    051public class EventListenerList {
    052    /** A list with the listeners added to this object. */
    053    private final List<EventListenerRegistrationData<?>> listeners;
    054
    055    /**
    056     * Creates a new instance of {@code EventListenerList}.
    057     */
    058    public EventListenerList() {
    059        listeners = new CopyOnWriteArrayList<>();
    060    }
    061
    062    /**
    063     * Adds an event listener for the specified event type. This listener is notified about events of this type and all its
    064     * sub types.
    065     *
    066     * @param type the event type (must not be <b>null</b>)
    067     * @param listener the listener to be registered (must not be <b>null</b>)
    068     * @param <T> the type of events processed by this listener
    069     * @throws IllegalArgumentException if a required parameter is <b>null</b>
    070     */
    071    public <T extends Event> void addEventListener(final EventType<T> type, final EventListener<? super T> listener) {
    072        listeners.add(new EventListenerRegistrationData<>(type, listener));
    073    }
    074
    075    /**
    076     * Adds the specified listener registration data object to the internal list of event listeners. This is an alternative
    077     * registration method; the event type and the listener are passed as a single data object.
    078     *
    079     * @param regData the registration data object (must not be <b>null</b>)
    080     * @param <T> the type of events processed by this listener
    081     * @throws IllegalArgumentException if the registration data object is <b>null</b>
    082     */
    083    public <T extends Event> void addEventListener(final EventListenerRegistrationData<T> regData) {
    084        if (regData == null) {
    085            throw new IllegalArgumentException("EventListenerRegistrationData must not be null!");
    086        }
    087        listeners.add(regData);
    088    }
    089
    090    /**
    091     * Removes the event listener registration for the given event type and listener. An event listener instance may be
    092     * registered multiple times for different event types. Therefore, when removing a listener the event type of the
    093     * registration in question has to be specified. The return value indicates whether a registration was removed. A value
    094     * of <b>false</b> means that no such combination of event type and listener was found.
    095     *
    096     * @param eventType the event type
    097     * @param listener the event listener to be removed
    098     * @param <T> the type of events processed by this listener
    099     * @return a flag whether a listener registration was removed
    100     */
    101    public <T extends Event> boolean removeEventListener(final EventType<T> eventType, final EventListener<? super T> listener) {
    102        return !(listener == null || eventType == null) && removeEventListener(new EventListenerRegistrationData<>(eventType, listener));
    103    }
    104
    105    /**
    106     * Removes the event listener registration defined by the passed in data object. This is an alternative method for
    107     * removing a listener which expects the event type and the listener in a single data object.
    108     *
    109     * @param regData the registration data object
    110     * @param <T> the type of events processed by this listener
    111     * @return a flag whether a listener registration was removed
    112     * @see #removeEventListener(EventType, EventListener)
    113     */
    114    public <T extends Event> boolean removeEventListener(final EventListenerRegistrationData<T> regData) {
    115        return listeners.remove(regData);
    116    }
    117
    118    /**
    119     * Fires an event to all registered listeners matching the event type.
    120     *
    121     * @param event the event to be fired (must not be <b>null</b>)
    122     * @throws IllegalArgumentException if the event is <b>null</b>
    123     */
    124    public void fire(final Event event) {
    125        if (event == null) {
    126            throw new IllegalArgumentException("Event to be fired must not be null!");
    127        }
    128
    129        for (final EventListenerIterator<? extends Event> iterator = getEventListenerIterator(event.getEventType()); iterator.hasNext();) {
    130            iterator.invokeNextListenerUnchecked(event);
    131        }
    132    }
    133
    134    /**
    135     * Returns an {@code Iterable} allowing access to all event listeners stored in this list which are compatible with the
    136     * specified event type.
    137     *
    138     * @param eventType the event type object
    139     * @param <T> the event type
    140     * @return an {@code Iterable} with the selected event listeners
    141     */
    142    public <T extends Event> Iterable<EventListener<? super T>> getEventListeners(final EventType<T> eventType) {
    143        return () -> getEventListenerIterator(eventType);
    144    }
    145
    146    /**
    147     * Returns a specialized iterator for obtaining all event listeners stored in this list which are compatible with the
    148     * specified event type.
    149     *
    150     * @param eventType the event type object
    151     * @param <T> the event type
    152     * @return an {@code Iterator} with the selected event listeners
    153     */
    154    public <T extends Event> EventListenerIterator<T> getEventListenerIterator(final EventType<T> eventType) {
    155        return new EventListenerIterator<>(listeners.iterator(), eventType);
    156    }
    157
    158    /**
    159     * Returns an (unmodifiable) list with registration information about all event listeners registered at this object.
    160     *
    161     * @return a list with event listener registration information
    162     */
    163    public List<EventListenerRegistrationData<?>> getRegistrations() {
    164        return Collections.unmodifiableList(listeners);
    165    }
    166
    167    /**
    168     * Returns a list with {@code EventListenerRegistrationData} objects for all event listener registrations of the
    169     * specified event type or an event type having this type as super type (directly or indirectly). Note that this is the
    170     * opposite direction than querying event types for firing events: in this case event listener registrations are
    171     * searched which are super event types from a given type. This method in contrast returns event listener registrations
    172     * for listeners that extend a given super type.
    173     *
    174     * @param eventType the event type object
    175     * @param <T> the event type
    176     * @return a list with the matching event listener registration objects
    177     */
    178    public <T extends Event> List<EventListenerRegistrationData<? extends T>> getRegistrationsForSuperType(final EventType<T> eventType) {
    179        final Map<EventType<?>, Set<EventType<?>>> superTypes = new HashMap<>();
    180        final List<EventListenerRegistrationData<? extends T>> results = new LinkedList<>();
    181
    182        for (final EventListenerRegistrationData<?> reg : listeners) {
    183            Set<EventType<?>> base = superTypes.get(reg.getEventType());
    184            if (base == null) {
    185                base = EventType.fetchSuperEventTypes(reg.getEventType());
    186                superTypes.put(reg.getEventType(), base);
    187            }
    188            if (base.contains(eventType)) {
    189                @SuppressWarnings("unchecked")
    190                final
    191                // This is safe because we just did a check
    192                EventListenerRegistrationData<? extends T> result = (EventListenerRegistrationData<? extends T>) reg;
    193                results.add(result);
    194            }
    195        }
    196
    197        return results;
    198    }
    199
    200    /**
    201     * Removes all event listeners registered at this object.
    202     */
    203    public void clear() {
    204        listeners.clear();
    205    }
    206
    207    /**
    208     * Adds all event listener registrations stored in the specified {@code EventListenerList} to this list.
    209     *
    210     * @param c the list to be copied (must not be <b>null</b>)
    211     * @throws IllegalArgumentException if the list to be copied is <b>null</b>
    212     */
    213    public void addAll(final EventListenerList c) {
    214        if (c == null) {
    215            throw new IllegalArgumentException("List to be copied must not be null!");
    216        }
    217
    218        for (final EventListenerRegistrationData<?> regData : c.getRegistrations()) {
    219            addEventListener(regData);
    220        }
    221    }
    222
    223    /**
    224     * Helper method for calling an event listener with an event. We have to operate on raw types to make this code compile.
    225     * However, this is safe because of the way the listeners have been registered and associated with event types - so it
    226     * is ensured that the event is compatible with the listener.
    227     *
    228     * @param listener the event listener to be called
    229     * @param event the event to be fired
    230     */
    231    @SuppressWarnings("unchecked")
    232    private static void callListener(final EventListener<?> listener, final Event event) {
    233        @SuppressWarnings("rawtypes")
    234        final EventListener rowListener = listener;
    235        rowListener.onEvent(event);
    236    }
    237
    238    /**
    239     * A special {@code Iterator} implementation used by the {@code getEventListenerIterator()} method. This iterator
    240     * returns only listeners compatible with a specified event type. It has a convenience method for invoking the current
    241     * listener in the iteration with an event.
    242     *
    243     * @param <T> the event type
    244     */
    245    public static final class EventListenerIterator<T extends Event> implements Iterator<EventListener<? super T>> {
    246        /** The underlying iterator. */
    247        private final Iterator<EventListenerRegistrationData<?>> underlyingIterator;
    248
    249        /** The base event type. */
    250        private final EventType<T> baseEventType;
    251
    252        /** The set with accepted event types. */
    253        private final Set<EventType<?>> acceptedTypes;
    254
    255        /** The next element in the iteration. */
    256        private EventListener<? super T> nextElement;
    257
    258        private EventListenerIterator(final Iterator<EventListenerRegistrationData<?>> it, final EventType<T> base) {
    259            underlyingIterator = it;
    260            baseEventType = base;
    261            acceptedTypes = EventType.fetchSuperEventTypes(base);
    262            initNextElement();
    263        }
    264
    265        @Override
    266        public boolean hasNext() {
    267            return nextElement != null;
    268        }
    269
    270        @Override
    271        public EventListener<? super T> next() {
    272            if (nextElement == null) {
    273                throw new NoSuchElementException("No more event listeners!");
    274            }
    275
    276            final EventListener<? super T> result = nextElement;
    277            initNextElement();
    278            return result;
    279        }
    280
    281        /**
    282         * Obtains the next event listener in this iteration and invokes it with the given event object.
    283         *
    284         * @param event the event object
    285         * @throws NoSuchElementException if iteration is at its end
    286         */
    287        public void invokeNext(final Event event) {
    288            validateEvent(event);
    289            invokeNextListenerUnchecked(event);
    290        }
    291
    292        /**
    293         * {@inheritDoc} This implementation always throws an exception. Removing elements is not supported.
    294         */
    295        @Override
    296        public void remove() {
    297            throw new UnsupportedOperationException("Removing elements is not supported!");
    298        }
    299
    300        /**
    301         * Determines the next element in the iteration.
    302         */
    303        private void initNextElement() {
    304            nextElement = null;
    305            while (underlyingIterator.hasNext() && nextElement == null) {
    306                final EventListenerRegistrationData<?> regData = underlyingIterator.next();
    307                if (acceptedTypes.contains(regData.getEventType())) {
    308                    nextElement = castListener(regData);
    309                }
    310            }
    311        }
    312
    313        /**
    314         * Checks whether the specified event can be passed to an event listener in this iteration. This check is done via the
    315         * hierarchy of event types.
    316         *
    317         * @param event the event object
    318         * @throws IllegalArgumentException if the event is invalid
    319         */
    320        private void validateEvent(final Event event) {
    321            if (event == null || !EventType.fetchSuperEventTypes(event.getEventType()).contains(baseEventType)) {
    322                throw new IllegalArgumentException("Event incompatible with listener iteration: " + event);
    323            }
    324        }
    325
    326        /**
    327         * Invokes the next event listener in the iteration without doing a validity check on the event. This method is called
    328         * internally to avoid duplicate event checks.
    329         *
    330         * @param event the event object
    331         */
    332        private void invokeNextListenerUnchecked(final Event event) {
    333            final EventListener<? super T> listener = next();
    334            callListener(listener, event);
    335        }
    336
    337        /**
    338         * Extracts the listener from the given data object and performs a cast to the target type. This is safe because it has
    339         * been checked before that the type is compatible.
    340         *
    341         * @param regData the data object
    342         * @return the extracted listener
    343         */
    344        @SuppressWarnings("unchecked")
    345        private EventListener<? super T> castListener(final EventListenerRegistrationData<?> regData) {
    346            @SuppressWarnings("rawtypes")
    347            final EventListener listener = regData.getListener();
    348            return listener;
    349        }
    350    }
    351}