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 */ 017 018package org.apache.commons.lang3.event; 019 020import java.io.ByteArrayOutputStream; 021import java.io.IOException; 022import java.io.ObjectInputStream; 023import java.io.ObjectOutputStream; 024import java.io.Serializable; 025import java.lang.reflect.InvocationHandler; 026import java.lang.reflect.InvocationTargetException; 027import java.lang.reflect.Method; 028import java.lang.reflect.Proxy; 029import java.util.ArrayList; 030import java.util.List; 031import java.util.Objects; 032import java.util.concurrent.CopyOnWriteArrayList; 033 034import org.apache.commons.lang3.ArrayUtils; 035import org.apache.commons.lang3.Validate; 036import org.apache.commons.lang3.exception.ExceptionUtils; 037import org.apache.commons.lang3.function.FailableConsumer; 038 039/** 040 * Manages a list of event listeners of a given generic type. This class provides {@link #addListener(Object)} and {@link #removeListener(Object)} methods for 041 * managing listeners, as well as a {@link #fire()} method for firing events to the listeners. 042 * 043 * <p> 044 * For example, to support ActionEvents: 045 * </p> 046 * 047 * <pre>{@code 048 * public class MyActionEventSource { 049 * 050 * private EventListenerSupport<ActionListener> actionListeners = EventListenerSupport.create(ActionListener.class); 051 * 052 * public void someMethodThatFiresAction() { 053 * ActionEvent e = new ActionEvent(this, ActionEvent.ACTION_PERFORMED, "something"); 054 * actionListeners.fire().actionPerformed(e); 055 * } 056 * } 057 * }</pre> 058 * <p> 059 * Events are fired 060 * <p> 061 * Serializing an {@link EventListenerSupport} instance will result in any non-{@link Serializable} listeners being silently dropped. 062 * </p> 063 * 064 * @param <L> the type of event listener that is supported by this proxy. 065 * @since 3.0 066 */ 067public class EventListenerSupport<L> implements Serializable { 068 069 /** 070 * Invokes listeners through {@link #invoke(Object, Method, Object[])} in the order added to the underlying {@link List}. 071 */ 072 protected class ProxyInvocationHandler implements InvocationHandler { 073 074 private final FailableConsumer<Throwable, IllegalAccessException> handler; 075 076 /** 077 * Constructs a new instance. 078 */ 079 public ProxyInvocationHandler() { 080 this(ExceptionUtils::rethrow); 081 } 082 083 /** 084 * Constructs a new instance. 085 * 086 * @param handler Handles Throwables. 087 * @since 3.15.0 088 */ 089 public ProxyInvocationHandler(final FailableConsumer<Throwable, IllegalAccessException> handler) { 090 this.handler = Objects.requireNonNull(handler); 091 } 092 093 /** 094 * Handles an exception thrown by a listener. By default rethrows the given Throwable. 095 * 096 * @param t The Throwable 097 * @throws IllegalAccessException thrown by the listener. 098 * @throws IllegalArgumentException thrown by the listener. 099 * @throws InvocationTargetException thrown by the listener. 100 * @since 3.15.0 101 */ 102 protected void handle(final Throwable t) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException { 103 handler.accept(t); 104 } 105 106 /** 107 * Propagates the method call to all registered listeners in place of the proxy listener object. 108 * <p> 109 * Calls listeners in the order added to the underlying {@link List}. 110 * </p> 111 * 112 * @param unusedProxy the proxy object representing a listener on which the invocation was called; not used 113 * @param method the listener method that will be called on all of the listeners. 114 * @param args event arguments to propagate to the listeners. 115 * @return the result of the method call 116 * @throws InvocationTargetException if an error occurs 117 * @throws IllegalArgumentException if an error occurs 118 * @throws IllegalAccessException if an error occurs 119 */ 120 @Override 121 public Object invoke(final Object unusedProxy, final Method method, final Object[] args) 122 throws IllegalAccessException, IllegalArgumentException, InvocationTargetException { 123 for (final L listener : listeners) { 124 try { 125 method.invoke(listener, args); 126 } catch (final Throwable t) { 127 handle(t); 128 } 129 } 130 return null; 131 } 132 } 133 134 /** Serialization version */ 135 private static final long serialVersionUID = 3593265990380473632L; 136 137 /** 138 * Creates an EventListenerSupport object which supports the specified 139 * listener type. 140 * 141 * @param <T> the type of the listener interface 142 * @param listenerInterface the type of listener interface that will receive 143 * events posted using this class. 144 * 145 * @return an EventListenerSupport object which supports the specified 146 * listener type. 147 * 148 * @throws NullPointerException if {@code listenerInterface} is 149 * {@code null}. 150 * @throws IllegalArgumentException if {@code listenerInterface} is 151 * not an interface. 152 */ 153 public static <T> EventListenerSupport<T> create(final Class<T> listenerInterface) { 154 return new EventListenerSupport<>(listenerInterface); 155 } 156 157 /** 158 * Hold the registered listeners. This list is intentionally a thread-safe copy-on-write-array so that traversals over the list of listeners will be atomic. 159 */ 160 private List<L> listeners = new CopyOnWriteArrayList<>(); 161 162 /** 163 * The proxy representing the collection of listeners. Calls to this proxy object will be sent to all registered listeners. 164 */ 165 private transient L proxy; 166 /** 167 * Empty typed array for #getListeners(). 168 */ 169 private transient L[] prototypeArray; 170 171 /** 172 * Constructs a new EventListenerSupport instance. 173 * <p> 174 * This constructor is needed for serialization. 175 * </p> 176 */ 177 private EventListenerSupport() { 178 } 179 180 /** 181 * Constructs an EventListenerSupport object which supports the provided 182 * listener interface. 183 * 184 * @param listenerInterface the type of listener interface that will receive 185 * events posted using this class. 186 * 187 * @throws NullPointerException if {@code listenerInterface} is 188 * {@code null}. 189 * @throws IllegalArgumentException if {@code listenerInterface} is 190 * not an interface. 191 */ 192 public EventListenerSupport(final Class<L> listenerInterface) { 193 this(listenerInterface, Thread.currentThread().getContextClassLoader()); 194 } 195 196 /** 197 * Constructs an EventListenerSupport object which supports the provided 198 * listener interface using the specified class loader to create the JDK 199 * dynamic proxy. 200 * 201 * @param listenerInterface the listener interface. 202 * @param classLoader the class loader. 203 * @throws NullPointerException if {@code listenerInterface} or 204 * {@code classLoader} is {@code null}. 205 * @throws IllegalArgumentException if {@code listenerInterface} is 206 * not an interface. 207 */ 208 public EventListenerSupport(final Class<L> listenerInterface, final ClassLoader classLoader) { 209 this(); 210 Objects.requireNonNull(listenerInterface, "listenerInterface"); 211 Objects.requireNonNull(classLoader, "classLoader"); 212 Validate.isTrue(listenerInterface.isInterface(), "Class %s is not an interface", listenerInterface.getName()); 213 initializeTransientFields(listenerInterface, classLoader); 214 } 215 216 /** 217 * Adds an event listener. 218 * <p> 219 * Listeners are called in the order added. 220 * </p> 221 * 222 * @param listener the event listener (may not be {@code null}). 223 * @throws NullPointerException if {@code listener} is {@code null}. 224 */ 225 public void addListener(final L listener) { 226 addListener(listener, true); 227 } 228 229 /** 230 * Adds an event listener. Will not add a pre-existing listener object to the list if {@code allowDuplicate} is false. 231 * <p> 232 * Listeners are called in the order added. 233 * </p> 234 * 235 * @param listener the event listener (may not be {@code null}). 236 * @param allowDuplicate the flag for determining if duplicate listener objects are allowed to be registered. 237 * 238 * @throws NullPointerException if {@code listener} is {@code null}. 239 * @since 3.5 240 */ 241 public void addListener(final L listener, final boolean allowDuplicate) { 242 Objects.requireNonNull(listener, "listener"); 243 if (allowDuplicate || !listeners.contains(listener)) { 244 listeners.add(listener); 245 } 246 } 247 248 /** 249 * Creates the {@link InvocationHandler} responsible for calling 250 * to the managed listeners. Subclasses can override to provide custom behavior. 251 * 252 * @return ProxyInvocationHandler 253 */ 254 protected InvocationHandler createInvocationHandler() { 255 return new ProxyInvocationHandler(); 256 } 257 258 /** 259 * Creates the proxy object. 260 * 261 * @param listenerInterface the class of the listener interface 262 * @param classLoader the class loader to be used 263 */ 264 private void createProxy(final Class<L> listenerInterface, final ClassLoader classLoader) { 265 proxy = listenerInterface.cast(Proxy.newProxyInstance(classLoader, new Class[] { listenerInterface }, createInvocationHandler())); 266 } 267 268 /** 269 * Returns a proxy object which can be used to call listener methods on all 270 * of the registered event listeners. All calls made to this proxy will be 271 * forwarded to all registered listeners. 272 * 273 * @return a proxy object which can be used to call listener methods on all 274 * of the registered event listeners 275 */ 276 public L fire() { 277 return proxy; 278 } 279 280 /** 281 * Gets the number of registered listeners. 282 * 283 * @return the number of registered listeners. 284 */ 285 int getListenerCount() { 286 return listeners.size(); 287 } 288 289 /** 290 * Gets an array containing the currently registered listeners. 291 * Modification to this array's elements will have no effect on the 292 * {@link EventListenerSupport} instance. 293 * @return L[] 294 */ 295 public L[] getListeners() { 296 return listeners.toArray(prototypeArray); 297 } 298 299 /** 300 * Initializes transient fields. 301 * 302 * @param listenerInterface the class of the listener interface 303 * @param classLoader the class loader to be used 304 */ 305 private void initializeTransientFields(final Class<L> listenerInterface, final ClassLoader classLoader) { 306 // Will throw CCE here if not correct 307 this.prototypeArray = ArrayUtils.newInstance(listenerInterface, 0); 308 createProxy(listenerInterface, classLoader); 309 } 310 311 /** 312 * Deserializes the next object into this instance. 313 * 314 * @param objectInputStream the input stream 315 * @throws IOException if an IO error occurs 316 * @throws ClassNotFoundException if the class cannot be resolved 317 */ 318 private void readObject(final ObjectInputStream objectInputStream) throws IOException, ClassNotFoundException { 319 @SuppressWarnings("unchecked") // Will throw CCE here if not correct 320 final L[] srcListeners = (L[]) objectInputStream.readObject(); 321 this.listeners = new CopyOnWriteArrayList<>(srcListeners); 322 final Class<L> listenerInterface = ArrayUtils.getComponentType(srcListeners); 323 initializeTransientFields(listenerInterface, Thread.currentThread().getContextClassLoader()); 324 } 325 326 /** 327 * Removes an event listener. 328 * 329 * @param listener the event listener (may not be {@code null}). 330 * @throws NullPointerException if {@code listener} is 331 * {@code null}. 332 */ 333 public void removeListener(final L listener) { 334 listeners.remove(Objects.requireNonNull(listener, "listener")); 335 } 336 337 /** 338 * Serializes this instance onto the given ObjectOutputStream. 339 * 340 * @param objectOutputStream the output stream 341 * @throws IOException if an IO error occurs 342 */ 343 private void writeObject(final ObjectOutputStream objectOutputStream) throws IOException { 344 final ArrayList<L> serializableListeners = new ArrayList<>(); 345 // Don't just rely on instanceof Serializable: 346 ObjectOutputStream testObjectOutputStream = new ObjectOutputStream(new ByteArrayOutputStream()); 347 for (final L listener : listeners) { 348 try { 349 testObjectOutputStream.writeObject(listener); 350 serializableListeners.add(listener); 351 } catch (final IOException exception) { 352 //recreate test stream in case of indeterminate state 353 testObjectOutputStream = new ObjectOutputStream(new ByteArrayOutputStream()); 354 } 355 } 356 // We can reconstitute everything we need from an array of our listeners, 357 // which has the additional advantage of typically requiring less storage than a list: 358 objectOutputStream.writeObject(serializableListeners.toArray(prototypeArray)); 359 } 360}