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