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    *     https://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      /**
53       * A special {@code Iterator} implementation used by the {@code getEventListenerIterator()} method. This iterator
54       * returns only listeners compatible with a specified event type. It has a convenience method for invoking the current
55       * listener in the iteration with an event.
56       *
57       * @param <T> the event type
58       */
59      public static final class EventListenerIterator<T extends Event> implements Iterator<EventListener<? super T>> {
60          /** The underlying iterator. */
61          private final Iterator<EventListenerRegistrationData<?>> underlyingIterator;
62  
63          /** The base event type. */
64          private final EventType<T> baseEventType;
65  
66          /** The set with accepted event types. */
67          private final Set<EventType<?>> acceptedTypes;
68  
69          /** The next element in the iteration. */
70          private EventListener<? super T> nextElement;
71  
72          private EventListenerIterator(final Iterator<EventListenerRegistrationData<?>> it, final EventType<T> base) {
73              underlyingIterator = it;
74              baseEventType = base;
75              acceptedTypes = EventType.fetchSuperEventTypes(base);
76              initNextElement();
77          }
78  
79          /**
80           * Extracts the listener from the given data object and performs a cast to the target type. This is safe because it has
81           * been checked before that the type is compatible.
82           *
83           * @param regData the data object
84           * @return the extracted listener
85           */
86          @SuppressWarnings("unchecked")
87          private EventListener<? super T> castListener(final EventListenerRegistrationData<?> regData) {
88              @SuppressWarnings("rawtypes")
89              final EventListener listener = regData.getListener();
90              return listener;
91          }
92  
93          @Override
94          public boolean hasNext() {
95              return nextElement != null;
96          }
97  
98          /**
99           * Determines the next element in the iteration.
100          */
101         private void initNextElement() {
102             nextElement = null;
103             while (underlyingIterator.hasNext() && nextElement == null) {
104                 final EventListenerRegistrationData<?> regData = underlyingIterator.next();
105                 if (acceptedTypes.contains(regData.getEventType())) {
106                     nextElement = castListener(regData);
107                 }
108             }
109         }
110 
111         /**
112          * Obtains the next event listener in this iteration and invokes it with the given event object.
113          *
114          * @param event the event object
115          * @throws NoSuchElementException if iteration is at its end
116          */
117         public void invokeNext(final Event event) {
118             validateEvent(event);
119             invokeNextListenerUnchecked(event);
120         }
121 
122         /**
123          * Invokes the next event listener in the iteration without doing a validity check on the event. This method is called
124          * internally to avoid duplicate event checks.
125          *
126          * @param event the event object
127          */
128         private void invokeNextListenerUnchecked(final Event event) {
129             callListener(next(), event);
130         }
131 
132         @Override
133         public EventListener<? super T> next() {
134             if (nextElement == null) {
135                 throw new NoSuchElementException("No more event listeners!");
136             }
137 
138             final EventListener<? super T> result = nextElement;
139             initNextElement();
140             return result;
141         }
142 
143         /**
144          * {@inheritDoc} This implementation always throws an exception. Removing elements is not supported.
145          */
146         @Override
147         public void remove() {
148             throw new UnsupportedOperationException("Removing elements is not supported!");
149         }
150 
151         /**
152          * Checks whether the specified event can be passed to an event listener in this iteration. This check is done via the
153          * hierarchy of event types.
154          *
155          * @param event the event object
156          * @throws IllegalArgumentException if the event is invalid
157          */
158         private void validateEvent(final Event event) {
159             if (event == null || !EventType.fetchSuperEventTypes(event.getEventType()).contains(baseEventType)) {
160                 throw new IllegalArgumentException("Event incompatible with listener iteration: " + event);
161             }
162         }
163     }
164 
165     /**
166      * Helper method for calling an event listener with an event. We have to operate on raw types to make this code compile.
167      * However, this is safe because of the way the listeners have been registered and associated with event types - so it
168      * is ensured that the event is compatible with the listener.
169      *
170      * @param listener the event listener to be called
171      * @param event the event to be fired
172      */
173     @SuppressWarnings("unchecked")
174     private static void callListener(final EventListener<?> listener, final Event event) {
175         @SuppressWarnings("rawtypes")
176         final EventListener rowListener = listener;
177         rowListener.onEvent(event);
178     }
179 
180     /** A list with the listeners added to this object. */
181     private final List<EventListenerRegistrationData<?>> listeners;
182 
183     /**
184      * Creates a new instance of {@code EventListenerList}.
185      */
186     public EventListenerList() {
187         listeners = new CopyOnWriteArrayList<>();
188     }
189 
190     /**
191      * Adds all event listener registrations stored in the specified {@code EventListenerList} to this list.
192      *
193      * @param c the list to be copied (must not be <strong>null</strong>)
194      * @throws IllegalArgumentException if the list to be copied is <strong>null</strong>
195      */
196     public void addAll(final EventListenerList c) {
197         if (c == null) {
198             throw new IllegalArgumentException("List to be copied must not be null!");
199         }
200         c.getRegistrations().forEach(this::addEventListener);
201     }
202 
203     /**
204      * Adds the specified listener registration data object to the internal list of event listeners. This is an alternative
205      * registration method; the event type and the listener are passed as a single data object.
206      *
207      * @param regData the registration data object (must not be <strong>null</strong>)
208      * @param <T> the type of events processed by this listener
209      * @throws IllegalArgumentException if the registration data object is <strong>null</strong>
210      */
211     public <T extends Event> void addEventListener(final EventListenerRegistrationData<T> regData) {
212         if (regData == null) {
213             throw new IllegalArgumentException("EventListenerRegistrationData must not be null!");
214         }
215         listeners.add(regData);
216     }
217 
218     /**
219      * Adds an event listener for the specified event type. This listener is notified about events of this type and all its
220      * sub types.
221      *
222      * @param type the event type (must not be <strong>null</strong>)
223      * @param listener the listener to be registered (must not be <strong>null</strong>)
224      * @param <T> the type of events processed by this listener
225      * @throws IllegalArgumentException if a required parameter is <strong>null</strong>
226      */
227     public <T extends Event> void addEventListener(final EventType<T> type, final EventListener<? super T> listener) {
228         listeners.add(new EventListenerRegistrationData<>(type, listener));
229     }
230 
231     /**
232      * Removes all event listeners registered at this object.
233      */
234     public void clear() {
235         listeners.clear();
236     }
237 
238     /**
239      * Fires an event to all registered listeners matching the event type.
240      *
241      * @param event the event to be fired (must not be <strong>null</strong>)
242      * @throws IllegalArgumentException if the event is <strong>null</strong>
243      */
244     public void fire(final Event event) {
245         if (event == null) {
246             throw new IllegalArgumentException("Event to be fired must not be null!");
247         }
248 
249         for (final EventListenerIterator<? extends Event> iterator = getEventListenerIterator(event.getEventType()); iterator.hasNext();) {
250             iterator.invokeNextListenerUnchecked(event);
251         }
252     }
253 
254     /**
255      * Gets a specialized iterator for obtaining all event listeners stored in this list which are compatible with the
256      * specified event type.
257      *
258      * @param eventType the event type object
259      * @param <T> the event type
260      * @return an {@code Iterator} with the selected event listeners
261      */
262     public <T extends Event> EventListenerIterator<T> getEventListenerIterator(final EventType<T> eventType) {
263         return new EventListenerIterator<>(listeners.iterator(), eventType);
264     }
265 
266     /**
267      * Gets an {@code Iterable} allowing access to all event listeners stored in this list which are compatible with the
268      * specified event type.
269      *
270      * @param eventType the event type object
271      * @param <T> the event type
272      * @return an {@code Iterable} with the selected event listeners
273      */
274     public <T extends Event> Iterable<EventListener<? super T>> getEventListeners(final EventType<T> eventType) {
275         return () -> getEventListenerIterator(eventType);
276     }
277 
278     /**
279      * Gets an (unmodifiable) list with registration information about all event listeners registered at this object.
280      *
281      * @return a list with event listener registration information
282      */
283     public List<EventListenerRegistrationData<?>> getRegistrations() {
284         return Collections.unmodifiableList(listeners);
285     }
286 
287     /**
288      * Gets a list with {@code EventListenerRegistrationData} objects for all event listener registrations of the
289      * specified event type or an event type having this type as super type (directly or indirectly). Note that this is the
290      * opposite direction than querying event types for firing events: in this case event listener registrations are
291      * searched which are super event types from a given type. This method in contrast returns event listener registrations
292      * for listeners that extend a given super type.
293      *
294      * @param eventType the event type object
295      * @param <T> the event type
296      * @return a list with the matching event listener registration objects
297      */
298     public <T extends Event> List<EventListenerRegistrationData<? extends T>> getRegistrationsForSuperType(final EventType<T> eventType) {
299         final Map<EventType<?>, Set<EventType<?>>> superTypes = new HashMap<>();
300         final List<EventListenerRegistrationData<? extends T>> results = new LinkedList<>();
301 
302         listeners.forEach(reg -> {
303             final Set<EventType<?>> base = superTypes.computeIfAbsent(reg.getEventType(), EventType::fetchSuperEventTypes);
304             if (base.contains(eventType)) {
305                 @SuppressWarnings("unchecked")
306                 final
307                 // This is safe because we just did a check
308                 EventListenerRegistrationData<? extends T> result = (EventListenerRegistrationData<? extends T>) reg;
309                 results.add(result);
310             }
311         });
312 
313         return results;
314     }
315 
316     /**
317      * Removes the event listener registration defined by the passed in data object. This is an alternative method for
318      * removing a listener which expects the event type and the listener in a single data object.
319      *
320      * @param regData the registration data object
321      * @param <T> the type of events processed by this listener
322      * @return a flag whether a listener registration was removed
323      * @see #removeEventListener(EventType, EventListener)
324      */
325     public <T extends Event> boolean removeEventListener(final EventListenerRegistrationData<T> regData) {
326         return listeners.remove(regData);
327     }
328 
329     /**
330      * Removes the event listener registration for the given event type and listener. An event listener instance may be
331      * registered multiple times for different event types. Therefore, when removing a listener the event type of the
332      * registration in question has to be specified. The return value indicates whether a registration was removed. A value
333      * of <strong>false</strong> means that no such combination of event type and listener was found.
334      *
335      * @param eventType the event type
336      * @param listener the event listener to be removed
337      * @param <T> the type of events processed by this listener
338      * @return a flag whether a listener registration was removed
339      */
340     public <T extends Event> boolean removeEventListener(final EventType<T> eventType, final EventListener<? super T> listener) {
341         return !(listener == null || eventType == null) && removeEventListener(new EventListenerRegistrationData<>(eventType, listener));
342     }
343 }