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