    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
    035 * specific event types. Event types are specified using the {@link EventType}
    036 * class. Due to the type parameters in method signatures, it is guaranteed that
    037 * registered listeners are compatible with the event types they are interested
    038 * in.
    039 * </p>
    040 * <p>
    041 * There are also methods for firing events. Here all registered listeners are
    042 * determined - based on the event type specified at registration time - which
    043 * should receive the event to be fired. So basically, the event type at
    044 * listener registration serves as a filter criterion. Because of the
    045 * hierarchical nature of event types it can be determined in a fine-grained way
    046 * which events are propagated to which listeners. It is also possible to
    047 * register a listener multiple times for different event types.
    048 * </p>
    049 * <p>
    050 * Implementation note: This class is thread-safe.
    051 * </p>
    052 *
    053 * @since 2.0
    054 */
    055public class EventListenerList
    056{
    057    /** A list with the listeners added to this object. */
    058    private final List<EventListenerRegistrationData<?>> listeners;
    059
    060    /**
    061     * Creates a new instance of {@code EventListenerList}.
    062     */
    063    public EventListenerList()
    064    {
    065        listeners =
    066                new CopyOnWriteArrayList<>();
    067    }
    068
    069    /**
    070     * Adds an event listener for the specified event type. This listener is
    071     * notified about events of this type and all its sub types.
    072     *
    073     * @param type the event type (must not be <b>null</b>)
    074     * @param listener the listener to be registered (must not be <b>null</b>)
    075     * @param <T> the type of events processed by this listener
    076     * @throws IllegalArgumentException if a required parameter is <b>null</b>
    077     */
    078    public <T extends Event> void addEventListener(final EventType<T> type,
    079            final EventListener<? super T> listener)
    080    {
    081        listeners.add(new EventListenerRegistrationData<>(type, listener));
    082    }
    083
    084    /**
    085     * Adds the specified listener registration data object to the internal list
    086     * of event listeners. This is an alternative registration method; the event
    087     * type and the listener are passed as a single data object.
    088     *
    089     * @param regData the registration data object (must not be <b>null</b>)
    090     * @param <T> the type of events processed by this listener
    091     * @throws IllegalArgumentException if the registration data object is
    092     *         <b>null</b>
    093     */
    094    public <T extends Event> void addEventListener(
    095            final EventListenerRegistrationData<T> regData)
    096    {
    097        if (regData == null)
    098        {
    099            throw new IllegalArgumentException(
    100                    "EventListenerRegistrationData must not be null!");
    101        }
    102        listeners.add(regData);
    103    }
    104
    105    /**
    106     * Removes the event listener registration for the given event type and
    107     * listener. An event listener instance may be registered multiple times for
    108     * different event types. Therefore, when removing a listener the event type
    109     * of the registration in question has to be specified. The return value
    110     * indicates whether a registration was removed. A value of <b>false</b>
    111     * means that no such combination of event type and listener was found.
    112     *
    113     * @param eventType the event type
    114     * @param listener the event listener to be removed
    115     * @param <T> the type of events processed by this listener
    116     * @return a flag whether a listener registration was removed
    117     */
    118    public <T extends Event> boolean removeEventListener(
    119            final EventType<T> eventType, final EventListener<? super T> listener)
    120    {
    121        return !(listener == null || eventType == null)
    122                && removeEventListener(new EventListenerRegistrationData<>(
    123                        eventType, listener));
    124    }
    125
    126    /**
    127     * Removes the event listener registration defined by the passed in data
    128     * object. This is an alternative method for removing a listener which
    129     * expects the event type and the listener in a single data object.
    130     *
    131     * @param regData the registration data object
    132     * @param <T> the type of events processed by this listener
    133     * @return a flag whether a listener registration was removed
    134     * @see #removeEventListener(EventType, EventListener)
    135     */
    136    public <T extends Event> boolean removeEventListener(
    137            final EventListenerRegistrationData<T> regData)
    138    {
    139        return listeners.remove(regData);
    140    }
    141
    142    /**
    143     * Fires an event to all registered listeners matching the event type.
    144     *
    145     * @param event the event to be fired (must not be <b>null</b>)
    146     * @throws IllegalArgumentException if the event is <b>null</b>
    147     */
    148    public void fire(final Event event)
    149    {
    150        if (event == null)
    151        {
    152            throw new IllegalArgumentException(
    153                    "Event to be fired must not be null!");
    154        }
    155
    156        for (final EventListenerIterator<? extends Event> iterator =
    157                getEventListenerIterator(event.getEventType()); iterator
    158                .hasNext();)
    159        {
    160            iterator.invokeNextListenerUnchecked(event);
    161        }
    162    }
    163
    164    /**
    165     * Returns an {@code Iterable} allowing access to all event listeners stored
    166     * in this list which are compatible with the specified event type.
    167     *
    168     * @param eventType the event type object
    169     * @param <T> the event type
    170     * @return an {@code Iterable} with the selected event listeners
    171     */
    172    public <T extends Event> Iterable<EventListener<? super T>> getEventListeners(
    173            final EventType<T> eventType)
    174    {
    175        return new Iterable<EventListener<? super T>>()
    176        {
    177            @Override
    178            public Iterator<EventListener<? super T>> iterator()
    179            {
    180                return getEventListenerIterator(eventType);
    181            }
    182        };
    183    }
    184
    185    /**
    186     * Returns a specialized iterator for obtaining all event listeners stored
    187     * in this list which are compatible with the specified event type.
    188     *
    189     * @param eventType the event type object
    190     * @param <T> the event type
    191     * @return an {@code Iterator} with the selected event listeners
    192     */
    193    public <T extends Event> EventListenerIterator<T> getEventListenerIterator(
    194            final EventType<T> eventType)
    195    {
    196        return new EventListenerIterator<>(listeners.iterator(), eventType);
    197    }
    198
    199    /**
    200     * Returns an (unmodifiable) list with registration information about all
    201     * event listeners registered at this object.
    202     *
    203     * @return a list with event listener registration information
    204     */
    205    public List<EventListenerRegistrationData<?>> getRegistrations()
    206    {
    207        return Collections.unmodifiableList(listeners);
    208    }
    209
    210    /**
    211     * Returns a list with {@code EventListenerRegistrationData} objects for all
    212     * event listener registrations of the specified event type or an event type
    213     * having this type as super type (directly or indirectly). Note that this
    214     * is the opposite direction than querying event types for firing events: in
    215     * this case event listener registrations are searched which are super event
    216     * types from a given type. This method in contrast returns event listener
    217     * registrations for listeners that extend a given super type.
    218     *
    219     * @param eventType the event type object
    220     * @param <T> the event type
    221     * @return a list with the matching event listener registration objects
    222     */
    223    public <T extends Event> List<EventListenerRegistrationData<? extends T>> getRegistrationsForSuperType(
    224            final EventType<T> eventType)
    225    {
    226        final Map<EventType<?>, Set<EventType<?>>> superTypes =
    227                new HashMap<>();
    228        final List<EventListenerRegistrationData<? extends T>> results =
    229                new LinkedList<>();
    230
    231        for (final EventListenerRegistrationData<?> reg : listeners)
    232        {
    233            Set<EventType<?>> base = superTypes.get(reg.getEventType());
    234            if (base == null)
    235            {
    236                base = EventType.fetchSuperEventTypes(reg.getEventType());
    237                superTypes.put(reg.getEventType(), base);
    238            }
    239            if (base.contains(eventType))
    240            {
    241                @SuppressWarnings("unchecked")
    242                final
    243                // This is safe because we just did a check
    244                EventListenerRegistrationData<? extends T> result =
    245                        (EventListenerRegistrationData<? extends T>) reg;
    246                results.add(result);
    247            }
    248        }
    249
    250        return results;
    251    }
    252
    253    /**
    254     * Removes all event listeners registered at this object.
    255     */
    256    public void clear()
    257    {
    258        listeners.clear();
    259    }
    260
    261    /**
    262     * Adds all event listener registrations stored in the specified
    263     * {@code EventListenerList} to this list.
    264     *
    265     * @param c the list to be copied (must not be <b>null</b>)
    266     * @throws IllegalArgumentException if the list to be copied is <b>null</b>
    267     */
    268    public void addAll(final EventListenerList c)
    269    {
    270        if (c == null)
    271        {
    272            throw new IllegalArgumentException(
    273                    "List to be copied must not be null!");
    274        }
    275
    276        for (final EventListenerRegistrationData<?> regData : c.getRegistrations())
    277        {
    278            addEventListener(regData);
    279        }
    280    }
    281
    282    /**
    283     * Helper method for calling an event listener with an event. We have to
    284     * operate on raw types to make this code compile. However, this is safe
    285     * because of the way the listeners have been registered and associated with
    286     * event types - so it is ensured that the event is compatible with the
    287     * listener.
    288     *
    289     * @param listener the event listener to be called
    290     * @param event the event to be fired
    291     */
    292    @SuppressWarnings("unchecked")
    293    private static void callListener(final EventListener<?> listener, final Event event)
    294    {
    295        @SuppressWarnings("rawtypes")
    296        final
    297        EventListener rowListener = listener;
    298        rowListener.onEvent(event);
    299    }
    300
    301    /**
    302     * A special {@code Iterator} implementation used by the
    303     * {@code getEventListenerIterator()} method. This iterator returns only
    304     * listeners compatible with a specified event type. It has a convenience
    305     * method for invoking the current listener in the iteration with an event.
    306     *
    307     * @param <T> the event type
    308     */
    309    public static final class EventListenerIterator<T extends Event> implements
    310            Iterator<EventListener<? super T>>
    311    {
    312        /** The underlying iterator. */
    313        private final Iterator<EventListenerRegistrationData<?>> underlyingIterator;
    314
    315        /** The base event type. */
    316        private final EventType<T> baseEventType;
    317
    318        /** The set with accepted event types. */
    319        private final Set<EventType<?>> acceptedTypes;
    320
    321        /** The next element in the iteration. */
    322        private EventListener<? super T> nextElement;
    323
    324        private EventListenerIterator(
    325                final Iterator<EventListenerRegistrationData<?>> it, final EventType<T> base)
    326        {
    327            underlyingIterator = it;
    328            baseEventType = base;
    329            acceptedTypes = EventType.fetchSuperEventTypes(base);
    330            initNextElement();
    331        }
    332
    333        @Override
    334        public boolean hasNext()
    335        {
    336            return nextElement != null;
    337        }
    338
    339        @Override
    340        public EventListener<? super T> next()
    341        {
    342            if (nextElement == null)
    343            {
    344                throw new NoSuchElementException("No more event listeners!");
    345            }
    346
    347            final EventListener<? super T> result = nextElement;
    348            initNextElement();
    349            return result;
    350        }
    351
    352        /**
    353         * Obtains the next event listener in this iteration and invokes it with
    354         * the given event object.
    355         *
    356         * @param event the event object
    357         * @throws NoSuchElementException if iteration is at its end
    358         */
    359        public void invokeNext(final Event event)
    360        {
    361            validateEvent(event);
    362            invokeNextListenerUnchecked(event);
    363        }
    364
    365        /**
    366         * {@inheritDoc} This implementation always throws an exception.
    367         * Removing elements is not supported.
    368         */
    369        @Override
    370        public void remove()
    371        {
    372            throw new UnsupportedOperationException(
    373                    "Removing elements is not supported!");
    374        }
    375
    376        /**
    377         * Determines the next element in the iteration.
    378         */
    379        private void initNextElement()
    380        {
    381            nextElement = null;
    382            while (underlyingIterator.hasNext() && nextElement == null)
    383            {
    384                final EventListenerRegistrationData<?> regData =
    385                        underlyingIterator.next();
    386                if (acceptedTypes.contains(regData.getEventType()))
    387                {
    388                    nextElement = castListener(regData);
    389                }
    390            }
    391        }
    392
    393        /**
    394         * Checks whether the specified event can be passed to an event listener
    395         * in this iteration. This check is done via the hierarchy of event
    396         * types.
    397         *
    398         * @param event the event object
    399         * @throws IllegalArgumentException if the event is invalid
    400         */
    401        private void validateEvent(final Event event)
    402        {
    403            if (event == null
    404                    || !EventType.fetchSuperEventTypes(event.getEventType()).contains(
    405                    baseEventType))
    406            {
    407                throw new IllegalArgumentException(
    408                        "Event incompatible with listener iteration: " + event);
    409            }
    410        }
    411
    412        /**
    413         * Invokes the next event listener in the iteration without doing a
    414         * validity check on the event. This method is called internally to
    415         * avoid duplicate event checks.
    416         *
    417         * @param event the event object
    418         */
    419        private void invokeNextListenerUnchecked(final Event event)
    420        {
    421            final EventListener<? super T> listener = next();
    422            callListener(listener, event);
    423        }
    424
    425        /**
    426         * Extracts the listener from the given data object and performs a cast
    427         * to the target type. This is safe because it has been checked before
    428         * that the type is compatible.
    429         *
    430         * @param regData the data object
    431         * @return the extracted listener
    432         */
    433        @SuppressWarnings("unchecked")
    434        private EventListener<? super T> castListener(
    435                final EventListenerRegistrationData<?> regData)
    436        {
    437            @SuppressWarnings("rawtypes")
    438            final
    439            EventListener listener = regData.getListener();
    440            return listener;
    441        }
    442    }
    443}