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 035 * specific event types. Event types are specified using the {@link EventType} 036 * class. Due to the type parameters in method signatures, it is guaranteed that 037 * registered listeners are compatible with the event types they are interested 038 * in. 039 * </p> 040 * <p> 041 * There are also methods for firing events. Here all registered listeners are 042 * determined - based on the event type specified at registration time - which 043 * should receive the event to be fired. So basically, the event type at 044 * listener registration serves as a filter criterion. Because of the 045 * hierarchical nature of event types it can be determined in a fine-grained way 046 * which events are propagated to which listeners. It is also possible to 047 * register a listener multiple times for different event types. 048 * </p> 049 * <p> 050 * Implementation note: This class is thread-safe. 051 * </p> 052 * 053 * @version $Id: EventListenerList.java 1842194 2018-09-27 22:24:23Z ggregory $ 054 * @since 2.0 055 */ 056public class EventListenerList 057{ 058 /** A list with the listeners added to this object. */ 059 private final List<EventListenerRegistrationData<?>> listeners; 060 061 /** 062 * Creates a new instance of {@code EventListenerList}. 063 */ 064 public EventListenerList() 065 { 066 listeners = 067 new CopyOnWriteArrayList<>(); 068 } 069 070 /** 071 * Adds an event listener for the specified event type. This listener is 072 * notified about events of this type and all its sub types. 073 * 074 * @param type the event type (must not be <b>null</b>) 075 * @param listener the listener to be registered (must not be <b>null</b>) 076 * @param <T> the type of events processed by this listener 077 * @throws IllegalArgumentException if a required parameter is <b>null</b> 078 */ 079 public <T extends Event> void addEventListener(final EventType<T> type, 080 final EventListener<? super T> listener) 081 { 082 listeners.add(new EventListenerRegistrationData<>(type, listener)); 083 } 084 085 /** 086 * Adds the specified listener registration data object to the internal list 087 * of event listeners. This is an alternative registration method; the event 088 * type and the listener are passed as a single data object. 089 * 090 * @param regData the registration data object (must not be <b>null</b>) 091 * @param <T> the type of events processed by this listener 092 * @throws IllegalArgumentException if the registration data object is 093 * <b>null</b> 094 */ 095 public <T extends Event> void addEventListener( 096 final EventListenerRegistrationData<T> regData) 097 { 098 if (regData == null) 099 { 100 throw new IllegalArgumentException( 101 "EventListenerRegistrationData must not be null!"); 102 } 103 listeners.add(regData); 104 } 105 106 /** 107 * Removes the event listener registration for the given event type and 108 * listener. An event listener instance may be registered multiple times for 109 * different event types. Therefore, when removing a listener the event type 110 * of the registration in question has to be specified. The return value 111 * indicates whether a registration was removed. A value of <b>false</b> 112 * means that no such combination of event type and listener was found. 113 * 114 * @param eventType the event type 115 * @param listener the event listener to be removed 116 * @param <T> the type of events processed by this listener 117 * @return a flag whether a listener registration was removed 118 */ 119 public <T extends Event> boolean removeEventListener( 120 final EventType<T> eventType, final EventListener<? super T> listener) 121 { 122 return !(listener == null || eventType == null) 123 && removeEventListener(new EventListenerRegistrationData<>( 124 eventType, listener)); 125 } 126 127 /** 128 * Removes the event listener registration defined by the passed in data 129 * object. This is an alternative method for removing a listener which 130 * expects the event type and the listener in a single data object. 131 * 132 * @param regData the registration data object 133 * @param <T> the type of events processed by this listener 134 * @return a flag whether a listener registration was removed 135 * @see #removeEventListener(EventType, EventListener) 136 */ 137 public <T extends Event> boolean removeEventListener( 138 final EventListenerRegistrationData<T> regData) 139 { 140 return listeners.remove(regData); 141 } 142 143 /** 144 * Fires an event to all registered listeners matching the event type. 145 * 146 * @param event the event to be fired (must not be <b>null</b>) 147 * @throws IllegalArgumentException if the event is <b>null</b> 148 */ 149 public void fire(final Event event) 150 { 151 if (event == null) 152 { 153 throw new IllegalArgumentException( 154 "Event to be fired must not be null!"); 155 } 156 157 for (final EventListenerIterator<? extends Event> iterator = 158 getEventListenerIterator(event.getEventType()); iterator 159 .hasNext();) 160 { 161 iterator.invokeNextListenerUnchecked(event); 162 } 163 } 164 165 /** 166 * Returns an {@code Iterable} allowing access to all event listeners stored 167 * in this list which are compatible with the specified event type. 168 * 169 * @param eventType the event type object 170 * @param <T> the event type 171 * @return an {@code Iterable} with the selected event listeners 172 */ 173 public <T extends Event> Iterable<EventListener<? super T>> getEventListeners( 174 final EventType<T> eventType) 175 { 176 return new Iterable<EventListener<? super T>>() 177 { 178 @Override 179 public Iterator<EventListener<? super T>> iterator() 180 { 181 return getEventListenerIterator(eventType); 182 } 183 }; 184 } 185 186 /** 187 * Returns a specialized iterator for obtaining all event listeners stored 188 * in this list which are compatible with the specified event type. 189 * 190 * @param eventType the event type object 191 * @param <T> the event type 192 * @return an {@code Iterator} with the selected event listeners 193 */ 194 public <T extends Event> EventListenerIterator<T> getEventListenerIterator( 195 final EventType<T> eventType) 196 { 197 return new EventListenerIterator<>(listeners.iterator(), eventType); 198 } 199 200 /** 201 * Returns an (unmodifiable) list with registration information about all 202 * event listeners registered at this object. 203 * 204 * @return a list with event listener registration information 205 */ 206 public List<EventListenerRegistrationData<?>> getRegistrations() 207 { 208 return Collections.unmodifiableList(listeners); 209 } 210 211 /** 212 * Returns a list with {@code EventListenerRegistrationData} objects for all 213 * event listener registrations of the specified event type or an event type 214 * having this type as super type (directly or indirectly). Note that this 215 * is the opposite direction than querying event types for firing events: in 216 * this case event listener registrations are searched which are super event 217 * types from a given type. This method in contrast returns event listener 218 * registrations for listeners that extend a given super type. 219 * 220 * @param eventType the event type object 221 * @param <T> the event type 222 * @return a list with the matching event listener registration objects 223 */ 224 public <T extends Event> List<EventListenerRegistrationData<? extends T>> getRegistrationsForSuperType( 225 final EventType<T> eventType) 226 { 227 final Map<EventType<?>, Set<EventType<?>>> superTypes = 228 new HashMap<>(); 229 final List<EventListenerRegistrationData<? extends T>> results = 230 new LinkedList<>(); 231 232 for (final EventListenerRegistrationData<?> reg : listeners) 233 { 234 Set<EventType<?>> base = superTypes.get(reg.getEventType()); 235 if (base == null) 236 { 237 base = EventType.fetchSuperEventTypes(reg.getEventType()); 238 superTypes.put(reg.getEventType(), base); 239 } 240 if (base.contains(eventType)) 241 { 242 @SuppressWarnings("unchecked") 243 final 244 // This is safe because we just did a check 245 EventListenerRegistrationData<? extends T> result = 246 (EventListenerRegistrationData<? extends T>) reg; 247 results.add(result); 248 } 249 } 250 251 return results; 252 } 253 254 /** 255 * Removes all event listeners registered at this object. 256 */ 257 public void clear() 258 { 259 listeners.clear(); 260 } 261 262 /** 263 * Adds all event listener registrations stored in the specified 264 * {@code EventListenerList} to this list. 265 * 266 * @param c the list to be copied (must not be <b>null</b>) 267 * @throws IllegalArgumentException if the list to be copied is <b>null</b> 268 */ 269 public void addAll(final EventListenerList c) 270 { 271 if (c == null) 272 { 273 throw new IllegalArgumentException( 274 "List to be copied must not be null!"); 275 } 276 277 for (final EventListenerRegistrationData<?> regData : c.getRegistrations()) 278 { 279 addEventListener(regData); 280 } 281 } 282 283 /** 284 * Helper method for calling an event listener with an event. We have to 285 * operate on raw types to make this code compile. However, this is safe 286 * because of the way the listeners have been registered and associated with 287 * event types - so it is ensured that the event is compatible with the 288 * listener. 289 * 290 * @param listener the event listener to be called 291 * @param event the event to be fired 292 */ 293 @SuppressWarnings("unchecked") 294 private static void callListener(final EventListener<?> listener, final Event event) 295 { 296 @SuppressWarnings("rawtypes") 297 final 298 EventListener rowListener = listener; 299 rowListener.onEvent(event); 300 } 301 302 /** 303 * A special {@code Iterator} implementation used by the 304 * {@code getEventListenerIterator()} method. This iterator returns only 305 * listeners compatible with a specified event type. It has a convenience 306 * method for invoking the current listener in the iteration with an event. 307 * 308 * @param <T> the event type 309 */ 310 public static final class EventListenerIterator<T extends Event> implements 311 Iterator<EventListener<? super T>> 312 { 313 /** The underlying iterator. */ 314 private final Iterator<EventListenerRegistrationData<?>> underlyingIterator; 315 316 /** The base event type. */ 317 private final EventType<T> baseEventType; 318 319 /** The set with accepted event types. */ 320 private final Set<EventType<?>> acceptedTypes; 321 322 /** The next element in the iteration. */ 323 private EventListener<? super T> nextElement; 324 325 private EventListenerIterator( 326 final Iterator<EventListenerRegistrationData<?>> it, final EventType<T> base) 327 { 328 underlyingIterator = it; 329 baseEventType = base; 330 acceptedTypes = EventType.fetchSuperEventTypes(base); 331 initNextElement(); 332 } 333 334 @Override 335 public boolean hasNext() 336 { 337 return nextElement != null; 338 } 339 340 @Override 341 public EventListener<? super T> next() 342 { 343 if (nextElement == null) 344 { 345 throw new NoSuchElementException("No more event listeners!"); 346 } 347 348 final EventListener<? super T> result = nextElement; 349 initNextElement(); 350 return result; 351 } 352 353 /** 354 * Obtains the next event listener in this iteration and invokes it with 355 * the given event object. 356 * 357 * @param event the event object 358 * @throws NoSuchElementException if iteration is at its end 359 */ 360 public void invokeNext(final Event event) 361 { 362 validateEvent(event); 363 invokeNextListenerUnchecked(event); 364 } 365 366 /** 367 * {@inheritDoc} This implementation always throws an exception. 368 * Removing elements is not supported. 369 */ 370 @Override 371 public void remove() 372 { 373 throw new UnsupportedOperationException( 374 "Removing elements is not supported!"); 375 } 376 377 /** 378 * Determines the next element in the iteration. 379 */ 380 private void initNextElement() 381 { 382 nextElement = null; 383 while (underlyingIterator.hasNext() && nextElement == null) 384 { 385 final EventListenerRegistrationData<?> regData = 386 underlyingIterator.next(); 387 if (acceptedTypes.contains(regData.getEventType())) 388 { 389 nextElement = castListener(regData); 390 } 391 } 392 } 393 394 /** 395 * Checks whether the specified event can be passed to an event listener 396 * in this iteration. This check is done via the hierarchy of event 397 * types. 398 * 399 * @param event the event object 400 * @throws IllegalArgumentException if the event is invalid 401 */ 402 private void validateEvent(final Event event) 403 { 404 if (event == null 405 || !EventType.fetchSuperEventTypes(event.getEventType()).contains( 406 baseEventType)) 407 { 408 throw new IllegalArgumentException( 409 "Event incompatible with listener iteration: " + event); 410 } 411 } 412 413 /** 414 * Invokes the next event listener in the iteration without doing a 415 * validity check on the event. This method is called internally to 416 * avoid duplicate event checks. 417 * 418 * @param event the event object 419 */ 420 private void invokeNextListenerUnchecked(final Event event) 421 { 422 final EventListener<? super T> listener = next(); 423 callListener(listener, event); 424 } 425 426 /** 427 * Extracts the listener from the given data object and performs a cast 428 * to the target type. This is safe because it has been checked before 429 * that the type is compatible. 430 * 431 * @param regData the data object 432 * @return the extracted listener 433 */ 434 @SuppressWarnings("unchecked") 435 private EventListener<? super T> castListener( 436 final EventListenerRegistrationData<?> regData) 437 { 438 @SuppressWarnings("rawtypes") 439 final 440 EventListener listener = regData.getListener(); 441 return listener; 442 } 443 } 444}