View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.configuration2.event;
18  
19  import java.util.Collections;
20  import java.util.HashMap;
21  import java.util.Iterator;
22  import java.util.LinkedList;
23  import java.util.List;
24  import java.util.Map;
25  import java.util.NoSuchElementException;
26  import java.util.Set;
27  import java.util.concurrent.CopyOnWriteArrayList;
28  
29  /**
30   * <p>
31   * A class for managing event listeners for an event source.
32   * </p>
33   * <p>
34   * This class allows registering an arbitrary number of event listeners for specific event types. Event types are
35   * specified using the {@link EventType} class. Due to the type parameters in method signatures, it is guaranteed that
36   * registered listeners are compatible with the event types they are interested in.
37   * </p>
38   * <p>
39   * There are also methods for firing events. Here all registered listeners are determined - based on the event type
40   * specified at registration time - which should receive the event to be fired. So basically, the event type at listener
41   * registration serves as a filter criterion. Because of the hierarchical nature of event types it can be determined in
42   * a fine-grained way which events are propagated to which listeners. It is also possible to register a listener
43   * multiple times for different event types.
44   * </p>
45   * <p>
46   * Implementation note: This class is thread-safe.
47   * </p>
48   *
49   * @since 2.0
50   */
51  public class EventListenerList {
52      /** A list with the listeners added to this object. */
53      private final List<EventListenerRegistrationData<?>> listeners;
54  
55      /**
56       * Creates a new instance of {@code EventListenerList}.
57       */
58      public EventListenerList() {
59          listeners = new CopyOnWriteArrayList<>();
60      }
61  
62      /**
63       * Adds an event listener for the specified event type. This listener is notified about events of this type and all its
64       * sub types.
65       *
66       * @param type the event type (must not be <b>null</b>)
67       * @param listener the listener to be registered (must not be <b>null</b>)
68       * @param <T> the type of events processed by this listener
69       * @throws IllegalArgumentException if a required parameter is <b>null</b>
70       */
71      public <T extends Event> void addEventListener(final EventType<T> type, final EventListener<? super T> listener) {
72          listeners.add(new EventListenerRegistrationData<>(type, listener));
73      }
74  
75      /**
76       * Adds the specified listener registration data object to the internal list of event listeners. This is an alternative
77       * registration method; the event type and the listener are passed as a single data object.
78       *
79       * @param regData the registration data object (must not be <b>null</b>)
80       * @param <T> the type of events processed by this listener
81       * @throws IllegalArgumentException if the registration data object is <b>null</b>
82       */
83      public <T extends Event> void addEventListener(final EventListenerRegistrationData<T> regData) {
84          if (regData == null) {
85              throw new IllegalArgumentException("EventListenerRegistrationData must not be null!");
86          }
87          listeners.add(regData);
88      }
89  
90      /**
91       * Removes the event listener registration for the given event type and listener. An event listener instance may be
92       * registered multiple times for different event types. Therefore, when removing a listener the event type of the
93       * registration in question has to be specified. The return value indicates whether a registration was removed. A value
94       * of <b>false</b> means that no such combination of event type and listener was found.
95       *
96       * @param eventType the event type
97       * @param listener the event listener to be removed
98       * @param <T> the type of events processed by this listener
99       * @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      * Gets 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      * Gets 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      * Gets 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      * Gets 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         listeners.forEach(reg -> {
183             final Set<EventType<?>> base = superTypes.computeIfAbsent(reg.getEventType(), EventType::fetchSuperEventTypes);
184             if (base.contains(eventType)) {
185                 @SuppressWarnings("unchecked")
186                 final
187                 // This is safe because we just did a check
188                 EventListenerRegistrationData<? extends T> result = (EventListenerRegistrationData<? extends T>) reg;
189                 results.add(result);
190             }
191         });
192 
193         return results;
194     }
195 
196     /**
197      * Removes all event listeners registered at this object.
198      */
199     public void clear() {
200         listeners.clear();
201     }
202 
203     /**
204      * Adds all event listener registrations stored in the specified {@code EventListenerList} to this list.
205      *
206      * @param c the list to be copied (must not be <b>null</b>)
207      * @throws IllegalArgumentException if the list to be copied is <b>null</b>
208      */
209     public void addAll(final EventListenerList c) {
210         if (c == null) {
211             throw new IllegalArgumentException("List to be copied must not be null!");
212         }
213         c.getRegistrations().forEach(this::addEventListener);
214     }
215 
216     /**
217      * Helper method for calling an event listener with an event. We have to operate on raw types to make this code compile.
218      * However, this is safe because of the way the listeners have been registered and associated with event types - so it
219      * is ensured that the event is compatible with the listener.
220      *
221      * @param listener the event listener to be called
222      * @param event the event to be fired
223      */
224     @SuppressWarnings("unchecked")
225     private static void callListener(final EventListener<?> listener, final Event event) {
226         @SuppressWarnings("rawtypes")
227         final EventListener rowListener = listener;
228         rowListener.onEvent(event);
229     }
230 
231     /**
232      * A special {@code Iterator} implementation used by the {@code getEventListenerIterator()} method. This iterator
233      * returns only listeners compatible with a specified event type. It has a convenience method for invoking the current
234      * listener in the iteration with an event.
235      *
236      * @param <T> the event type
237      */
238     public static final class EventListenerIterator<T extends Event> implements Iterator<EventListener<? super T>> {
239         /** The underlying iterator. */
240         private final Iterator<EventListenerRegistrationData<?>> underlyingIterator;
241 
242         /** The base event type. */
243         private final EventType<T> baseEventType;
244 
245         /** The set with accepted event types. */
246         private final Set<EventType<?>> acceptedTypes;
247 
248         /** The next element in the iteration. */
249         private EventListener<? super T> nextElement;
250 
251         private EventListenerIterator(final Iterator<EventListenerRegistrationData<?>> it, final EventType<T> base) {
252             underlyingIterator = it;
253             baseEventType = base;
254             acceptedTypes = EventType.fetchSuperEventTypes(base);
255             initNextElement();
256         }
257 
258         @Override
259         public boolean hasNext() {
260             return nextElement != null;
261         }
262 
263         @Override
264         public EventListener<? super T> next() {
265             if (nextElement == null) {
266                 throw new NoSuchElementException("No more event listeners!");
267             }
268 
269             final EventListener<? super T> result = nextElement;
270             initNextElement();
271             return result;
272         }
273 
274         /**
275          * Obtains the next event listener in this iteration and invokes it with the given event object.
276          *
277          * @param event the event object
278          * @throws NoSuchElementException if iteration is at its end
279          */
280         public void invokeNext(final Event event) {
281             validateEvent(event);
282             invokeNextListenerUnchecked(event);
283         }
284 
285         /**
286          * {@inheritDoc} This implementation always throws an exception. Removing elements is not supported.
287          */
288         @Override
289         public void remove() {
290             throw new UnsupportedOperationException("Removing elements is not supported!");
291         }
292 
293         /**
294          * Determines the next element in the iteration.
295          */
296         private void initNextElement() {
297             nextElement = null;
298             while (underlyingIterator.hasNext() && nextElement == null) {
299                 final EventListenerRegistrationData<?> regData = underlyingIterator.next();
300                 if (acceptedTypes.contains(regData.getEventType())) {
301                     nextElement = castListener(regData);
302                 }
303             }
304         }
305 
306         /**
307          * Checks whether the specified event can be passed to an event listener in this iteration. This check is done via the
308          * hierarchy of event types.
309          *
310          * @param event the event object
311          * @throws IllegalArgumentException if the event is invalid
312          */
313         private void validateEvent(final Event event) {
314             if (event == null || !EventType.fetchSuperEventTypes(event.getEventType()).contains(baseEventType)) {
315                 throw new IllegalArgumentException("Event incompatible with listener iteration: " + event);
316             }
317         }
318 
319         /**
320          * Invokes the next event listener in the iteration without doing a validity check on the event. This method is called
321          * internally to avoid duplicate event checks.
322          *
323          * @param event the event object
324          */
325         private void invokeNextListenerUnchecked(final Event event) {
326             callListener(next(), event);
327         }
328 
329         /**
330          * Extracts the listener from the given data object and performs a cast to the target type. This is safe because it has
331          * been checked before that the type is compatible.
332          *
333          * @param regData the data object
334          * @return the extracted listener
335          */
336         @SuppressWarnings("unchecked")
337         private EventListener<? super T> castListener(final EventListenerRegistrationData<?> regData) {
338             @SuppressWarnings("rawtypes")
339             final EventListener listener = regData.getListener();
340             return listener;
341         }
342     }
343 }