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