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 new Iterable<EventListener<? super T>>() 176 { 177 @Override 178 public Iterator<EventListener<? super T>> iterator() 179 { 180 return getEventListenerIterator(eventType); 181 } 182 }; 183 } 184 185 /** 186 * Returns a specialized iterator for obtaining all event listeners stored 187 * in this list which are compatible with the specified event type. 188 * 189 * @param eventType the event type object 190 * @param <T> the event type 191 * @return an {@code Iterator} with the selected event listeners 192 */ 193 public <T extends Event> EventListenerIterator<T> getEventListenerIterator( 194 final EventType<T> eventType) 195 { 196 return new EventListenerIterator<>(listeners.iterator(), eventType); 197 } 198 199 /** 200 * Returns an (unmodifiable) list with registration information about all 201 * event listeners registered at this object. 202 * 203 * @return a list with event listener registration information 204 */ 205 public List<EventListenerRegistrationData<?>> getRegistrations() 206 { 207 return Collections.unmodifiableList(listeners); 208 } 209 210 /** 211 * Returns a list with {@code EventListenerRegistrationData} objects for all 212 * event listener registrations of the specified event type or an event type 213 * having this type as super type (directly or indirectly). Note that this 214 * is the opposite direction than querying event types for firing events: in 215 * this case event listener registrations are searched which are super event 216 * types from a given type. This method in contrast returns event listener 217 * registrations for listeners that extend a given super type. 218 * 219 * @param eventType the event type object 220 * @param <T> the event type 221 * @return a list with the matching event listener registration objects 222 */ 223 public <T extends Event> List<EventListenerRegistrationData<? extends T>> getRegistrationsForSuperType( 224 final EventType<T> eventType) 225 { 226 final Map<EventType<?>, Set<EventType<?>>> superTypes = 227 new HashMap<>(); 228 final List<EventListenerRegistrationData<? extends T>> results = 229 new LinkedList<>(); 230 231 for (final EventListenerRegistrationData<?> reg : listeners) 232 { 233 Set<EventType<?>> base = superTypes.get(reg.getEventType()); 234 if (base == null) 235 { 236 base = EventType.fetchSuperEventTypes(reg.getEventType()); 237 superTypes.put(reg.getEventType(), base); 238 } 239 if (base.contains(eventType)) 240 { 241 @SuppressWarnings("unchecked") 242 final 243 // This is safe because we just did a check 244 EventListenerRegistrationData<? extends T> result = 245 (EventListenerRegistrationData<? extends T>) reg; 246 results.add(result); 247 } 248 } 249 250 return results; 251 } 252 253 /** 254 * Removes all event listeners registered at this object. 255 */ 256 public void clear() 257 { 258 listeners.clear(); 259 } 260 261 /** 262 * Adds all event listener registrations stored in the specified 263 * {@code EventListenerList} to this list. 264 * 265 * @param c the list to be copied (must not be <b>null</b>) 266 * @throws IllegalArgumentException if the list to be copied is <b>null</b> 267 */ 268 public void addAll(final EventListenerList c) 269 { 270 if (c == null) 271 { 272 throw new IllegalArgumentException( 273 "List to be copied must not be null!"); 274 } 275 276 for (final EventListenerRegistrationData<?> regData : c.getRegistrations()) 277 { 278 addEventListener(regData); 279 } 280 } 281 282 /** 283 * Helper method for calling an event listener with an event. We have to 284 * operate on raw types to make this code compile. However, this is safe 285 * because of the way the listeners have been registered and associated with 286 * event types - so it is ensured that the event is compatible with the 287 * listener. 288 * 289 * @param listener the event listener to be called 290 * @param event the event to be fired 291 */ 292 @SuppressWarnings("unchecked") 293 private static void callListener(final EventListener<?> listener, final Event event) 294 { 295 @SuppressWarnings("rawtypes") 296 final 297 EventListener rowListener = listener; 298 rowListener.onEvent(event); 299 } 300 301 /** 302 * A special {@code Iterator} implementation used by the 303 * {@code getEventListenerIterator()} method. This iterator returns only 304 * listeners compatible with a specified event type. It has a convenience 305 * method for invoking the current listener in the iteration with an event. 306 * 307 * @param <T> the event type 308 */ 309 public static final class EventListenerIterator<T extends Event> implements 310 Iterator<EventListener<? super T>> 311 { 312 /** The underlying iterator. */ 313 private final Iterator<EventListenerRegistrationData<?>> underlyingIterator; 314 315 /** The base event type. */ 316 private final EventType<T> baseEventType; 317 318 /** The set with accepted event types. */ 319 private final Set<EventType<?>> acceptedTypes; 320 321 /** The next element in the iteration. */ 322 private EventListener<? super T> nextElement; 323 324 private EventListenerIterator( 325 final Iterator<EventListenerRegistrationData<?>> it, final EventType<T> base) 326 { 327 underlyingIterator = it; 328 baseEventType = base; 329 acceptedTypes = EventType.fetchSuperEventTypes(base); 330 initNextElement(); 331 } 332 333 @Override 334 public boolean hasNext() 335 { 336 return nextElement != null; 337 } 338 339 @Override 340 public EventListener<? super T> next() 341 { 342 if (nextElement == null) 343 { 344 throw new NoSuchElementException("No more event listeners!"); 345 } 346 347 final EventListener<? super T> result = nextElement; 348 initNextElement(); 349 return result; 350 } 351 352 /** 353 * Obtains the next event listener in this iteration and invokes it with 354 * the given event object. 355 * 356 * @param event the event object 357 * @throws NoSuchElementException if iteration is at its end 358 */ 359 public void invokeNext(final Event event) 360 { 361 validateEvent(event); 362 invokeNextListenerUnchecked(event); 363 } 364 365 /** 366 * {@inheritDoc} This implementation always throws an exception. 367 * Removing elements is not supported. 368 */ 369 @Override 370 public void remove() 371 { 372 throw new UnsupportedOperationException( 373 "Removing elements is not supported!"); 374 } 375 376 /** 377 * Determines the next element in the iteration. 378 */ 379 private void initNextElement() 380 { 381 nextElement = null; 382 while (underlyingIterator.hasNext() && nextElement == null) 383 { 384 final EventListenerRegistrationData<?> regData = 385 underlyingIterator.next(); 386 if (acceptedTypes.contains(regData.getEventType())) 387 { 388 nextElement = castListener(regData); 389 } 390 } 391 } 392 393 /** 394 * Checks whether the specified event can be passed to an event listener 395 * in this iteration. This check is done via the hierarchy of event 396 * types. 397 * 398 * @param event the event object 399 * @throws IllegalArgumentException if the event is invalid 400 */ 401 private void validateEvent(final Event event) 402 { 403 if (event == null 404 || !EventType.fetchSuperEventTypes(event.getEventType()).contains( 405 baseEventType)) 406 { 407 throw new IllegalArgumentException( 408 "Event incompatible with listener iteration: " + event); 409 } 410 } 411 412 /** 413 * Invokes the next event listener in the iteration without doing a 414 * validity check on the event. This method is called internally to 415 * avoid duplicate event checks. 416 * 417 * @param event the event object 418 */ 419 private void invokeNextListenerUnchecked(final Event event) 420 { 421 final EventListener<? super T> listener = next(); 422 callListener(listener, event); 423 } 424 425 /** 426 * Extracts the listener from the given data object and performs a cast 427 * to the target type. This is safe because it has been checked before 428 * that the type is compatible. 429 * 430 * @param regData the data object 431 * @return the extracted listener 432 */ 433 @SuppressWarnings("unchecked") 434 private EventListener<? super T> castListener( 435 final EventListenerRegistrationData<?> regData) 436 { 437 @SuppressWarnings("rawtypes") 438 final 439 EventListener listener = regData.getListener(); 440 return listener; 441 } 442 } 443}