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 1790899 2017-04-10 21:56:46Z 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(EventType<T> type, 080 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 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 EventType<T> eventType, 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 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(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 (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 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 EventType<T> eventType) 226 { 227 Map<EventType<?>, Set<EventType<?>>> superTypes = 228 new HashMap<>(); 229 List<EventListenerRegistrationData<? extends T>> results = 230 new LinkedList<>(); 231 232 for (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 // 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(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 (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(EventListener<?> listener, Event event) 294 { 295 @SuppressWarnings("rawtypes") 296 EventListener rowListener = listener; 297 rowListener.onEvent(event); 298 } 299 300 /** 301 * A special {@code Iterator} implementation used by the 302 * {@code getEventListenerIterator()} method. This iterator returns only 303 * listeners compatible with a specified event type. It has a convenience 304 * method for invoking the current listener in the iteration with an event. 305 * 306 * @param <T> the event type 307 */ 308 public static final class EventListenerIterator<T extends Event> implements 309 Iterator<EventListener<? super T>> 310 { 311 /** The underlying iterator. */ 312 private final Iterator<EventListenerRegistrationData<?>> underlyingIterator; 313 314 /** The base event type. */ 315 private final EventType<T> baseEventType; 316 317 /** The set with accepted event types. */ 318 private final Set<EventType<?>> acceptedTypes; 319 320 /** The next element in the iteration. */ 321 private EventListener<? super T> nextElement; 322 323 private EventListenerIterator( 324 Iterator<EventListenerRegistrationData<?>> it, EventType<T> base) 325 { 326 underlyingIterator = it; 327 baseEventType = base; 328 acceptedTypes = EventType.fetchSuperEventTypes(base); 329 initNextElement(); 330 } 331 332 @Override 333 public boolean hasNext() 334 { 335 return nextElement != null; 336 } 337 338 @Override 339 public EventListener<? super T> next() 340 { 341 if (nextElement == null) 342 { 343 throw new NoSuchElementException("No more event listeners!"); 344 } 345 346 EventListener<? super T> result = nextElement; 347 initNextElement(); 348 return result; 349 } 350 351 /** 352 * Obtains the next event listener in this iteration and invokes it with 353 * the given event object. 354 * 355 * @param event the event object 356 * @throws NoSuchElementException if iteration is at its end 357 */ 358 public void invokeNext(Event event) 359 { 360 validateEvent(event); 361 invokeNextListenerUnchecked(event); 362 } 363 364 /** 365 * {@inheritDoc} This implementation always throws an exception. 366 * Removing elements is not supported. 367 */ 368 @Override 369 public void remove() 370 { 371 throw new UnsupportedOperationException( 372 "Removing elements is not supported!"); 373 } 374 375 /** 376 * Determines the next element in the iteration. 377 */ 378 private void initNextElement() 379 { 380 nextElement = null; 381 while (underlyingIterator.hasNext() && nextElement == null) 382 { 383 EventListenerRegistrationData<?> regData = 384 underlyingIterator.next(); 385 if (acceptedTypes.contains(regData.getEventType())) 386 { 387 nextElement = castListener(regData); 388 } 389 } 390 } 391 392 /** 393 * Checks whether the specified event can be passed to an event listener 394 * in this iteration. This check is done via the hierarchy of event 395 * types. 396 * 397 * @param event the event object 398 * @throws IllegalArgumentException if the event is invalid 399 */ 400 private void validateEvent(Event event) 401 { 402 if (event == null 403 || !EventType.fetchSuperEventTypes(event.getEventType()).contains( 404 baseEventType)) 405 { 406 throw new IllegalArgumentException( 407 "Event incompatible with listener iteration: " + event); 408 } 409 } 410 411 /** 412 * Invokes the next event listener in the iteration without doing a 413 * validity check on the event. This method is called internally to 414 * avoid duplicate event checks. 415 * 416 * @param event the event object 417 */ 418 private void invokeNextListenerUnchecked(Event event) 419 { 420 EventListener<? super T> listener = next(); 421 callListener(listener, event); 422 } 423 424 /** 425 * Extracts the listener from the given data object and performs a cast 426 * to the target type. This is safe because it has been checked before 427 * that the type is compatible. 428 * 429 * @param regData the data object 430 * @return the extracted listener 431 */ 432 @SuppressWarnings("unchecked") 433 private EventListener<? super T> castListener( 434 EventListenerRegistrationData<?> regData) 435 { 436 @SuppressWarnings("rawtypes") 437 EventListener listener = regData.getListener(); 438 return listener; 439 } 440 } 441}