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