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