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}