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 }