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}