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 }