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 () -> getEventListenerIterator(eventType);
176    }
177
178    /**
179     * Returns a specialized iterator for obtaining all event listeners stored
180     * in this list which are compatible with the specified event type.
181     *
182     * @param eventType the event type object
183     * @param <T> the event type
184     * @return an {@code Iterator} with the selected event listeners
185     */
186    public <T extends Event> EventListenerIterator<T> getEventListenerIterator(
187            final EventType<T> eventType)
188    {
189        return new EventListenerIterator<>(listeners.iterator(), eventType);
190    }
191
192    /**
193     * Returns an (unmodifiable) list with registration information about all
194     * event listeners registered at this object.
195     *
196     * @return a list with event listener registration information
197     */
198    public List<EventListenerRegistrationData<?>> getRegistrations()
199    {
200        return Collections.unmodifiableList(listeners);
201    }
202
203    /**
204     * Returns a list with {@code EventListenerRegistrationData} objects for all
205     * event listener registrations of the specified event type or an event type
206     * having this type as super type (directly or indirectly). Note that this
207     * is the opposite direction than querying event types for firing events: in
208     * this case event listener registrations are searched which are super event
209     * types from a given type. This method in contrast returns event listener
210     * registrations for listeners that extend a given super type.
211     *
212     * @param eventType the event type object
213     * @param <T> the event type
214     * @return a list with the matching event listener registration objects
215     */
216    public <T extends Event> List<EventListenerRegistrationData<? extends T>> getRegistrationsForSuperType(
217            final EventType<T> eventType)
218    {
219        final Map<EventType<?>, Set<EventType<?>>> superTypes =
220                new HashMap<>();
221        final List<EventListenerRegistrationData<? extends T>> results =
222                new LinkedList<>();
223
224        for (final EventListenerRegistrationData<?> reg : listeners)
225        {
226            Set<EventType<?>> base = superTypes.get(reg.getEventType());
227            if (base == null)
228            {
229                base = EventType.fetchSuperEventTypes(reg.getEventType());
230                superTypes.put(reg.getEventType(), base);
231            }
232            if (base.contains(eventType))
233            {
234                @SuppressWarnings("unchecked")
235                final
236                // This is safe because we just did a check
237                EventListenerRegistrationData<? extends T> result =
238                        (EventListenerRegistrationData<? extends T>) reg;
239                results.add(result);
240            }
241        }
242
243        return results;
244    }
245
246    /**
247     * Removes all event listeners registered at this object.
248     */
249    public void clear()
250    {
251        listeners.clear();
252    }
253
254    /**
255     * Adds all event listener registrations stored in the specified
256     * {@code EventListenerList} to this list.
257     *
258     * @param c the list to be copied (must not be <b>null</b>)
259     * @throws IllegalArgumentException if the list to be copied is <b>null</b>
260     */
261    public void addAll(final EventListenerList c)
262    {
263        if (c == null)
264        {
265            throw new IllegalArgumentException(
266                    "List to be copied must not be null!");
267        }
268
269        for (final EventListenerRegistrationData<?> regData : c.getRegistrations())
270        {
271            addEventListener(regData);
272        }
273    }
274
275    /**
276     * Helper method for calling an event listener with an event. We have to
277     * operate on raw types to make this code compile. However, this is safe
278     * because of the way the listeners have been registered and associated with
279     * event types - so it is ensured that the event is compatible with the
280     * listener.
281     *
282     * @param listener the event listener to be called
283     * @param event the event to be fired
284     */
285    @SuppressWarnings("unchecked")
286    private static void callListener(final EventListener<?> listener, final Event event)
287    {
288        @SuppressWarnings("rawtypes")
289        final
290        EventListener rowListener = listener;
291        rowListener.onEvent(event);
292    }
293
294    /**
295     * A special {@code Iterator} implementation used by the
296     * {@code getEventListenerIterator()} method. This iterator returns only
297     * listeners compatible with a specified event type. It has a convenience
298     * method for invoking the current listener in the iteration with an event.
299     *
300     * @param <T> the event type
301     */
302    public static final class EventListenerIterator<T extends Event> implements
303            Iterator<EventListener<? super T>>
304    {
305        /** The underlying iterator. */
306        private final Iterator<EventListenerRegistrationData<?>> underlyingIterator;
307
308        /** The base event type. */
309        private final EventType<T> baseEventType;
310
311        /** The set with accepted event types. */
312        private final Set<EventType<?>> acceptedTypes;
313
314        /** The next element in the iteration. */
315        private EventListener<? super T> nextElement;
316
317        private EventListenerIterator(
318                final Iterator<EventListenerRegistrationData<?>> it, final EventType<T> base)
319        {
320            underlyingIterator = it;
321            baseEventType = base;
322            acceptedTypes = EventType.fetchSuperEventTypes(base);
323            initNextElement();
324        }
325
326        @Override
327        public boolean hasNext()
328        {
329            return nextElement != null;
330        }
331
332        @Override
333        public EventListener<? super T> next()
334        {
335            if (nextElement == null)
336            {
337                throw new NoSuchElementException("No more event listeners!");
338            }
339
340            final EventListener<? super T> result = nextElement;
341            initNextElement();
342            return result;
343        }
344
345        /**
346         * Obtains the next event listener in this iteration and invokes it with
347         * the given event object.
348         *
349         * @param event the event object
350         * @throws NoSuchElementException if iteration is at its end
351         */
352        public void invokeNext(final Event event)
353        {
354            validateEvent(event);
355            invokeNextListenerUnchecked(event);
356        }
357
358        /**
359         * {@inheritDoc} This implementation always throws an exception.
360         * Removing elements is not supported.
361         */
362        @Override
363        public void remove()
364        {
365            throw new UnsupportedOperationException(
366                    "Removing elements is not supported!");
367        }
368
369        /**
370         * Determines the next element in the iteration.
371         */
372        private void initNextElement()
373        {
374            nextElement = null;
375            while (underlyingIterator.hasNext() && nextElement == null)
376            {
377                final EventListenerRegistrationData<?> regData =
378                        underlyingIterator.next();
379                if (acceptedTypes.contains(regData.getEventType()))
380                {
381                    nextElement = castListener(regData);
382                }
383            }
384        }
385
386        /**
387         * Checks whether the specified event can be passed to an event listener
388         * in this iteration. This check is done via the hierarchy of event
389         * types.
390         *
391         * @param event the event object
392         * @throws IllegalArgumentException if the event is invalid
393         */
394        private void validateEvent(final Event event)
395        {
396            if (event == null
397                    || !EventType.fetchSuperEventTypes(event.getEventType()).contains(
398                    baseEventType))
399            {
400                throw new IllegalArgumentException(
401                        "Event incompatible with listener iteration: " + event);
402            }
403        }
404
405        /**
406         * Invokes the next event listener in the iteration without doing a
407         * validity check on the event. This method is called internally to
408         * avoid duplicate event checks.
409         *
410         * @param event the event object
411         */
412        private void invokeNextListenerUnchecked(final Event event)
413        {
414            final EventListener<? super T> listener = next();
415            callListener(listener, event);
416        }
417
418        /**
419         * Extracts the listener from the given data object and performs a cast
420         * to the target type. This is safe because it has been checked before
421         * that the type is compatible.
422         *
423         * @param regData the data object
424         * @return the extracted listener
425         */
426        @SuppressWarnings("unchecked")
427        private EventListener<? super T> castListener(
428                final EventListenerRegistrationData<?> regData)
429        {
430            @SuppressWarnings("rawtypes")
431            final
432            EventListener listener = regData.getListener();
433            return listener;
434        }
435    }
436}