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 018 package org.apache.commons.lang3.event; 019 020 import java.io.ByteArrayOutputStream; 021 import java.io.IOException; 022 import java.io.ObjectInputStream; 023 import java.io.ObjectOutputStream; 024 import java.io.Serializable; 025 import java.lang.reflect.Array; 026 import java.lang.reflect.InvocationHandler; 027 import java.lang.reflect.Method; 028 import java.lang.reflect.Proxy; 029 import java.util.ArrayList; 030 import java.util.List; 031 import java.util.concurrent.CopyOnWriteArrayList; 032 033 import org.apache.commons.lang3.Validate; 034 035 /** 036 * An EventListenerSupport object can be used to manage a list of event 037 * listeners of a particular type. The class provides 038 * {@link #addListener(Object)} and {@link #removeListener(Object)} methods 039 * for registering listeners, as well as a {@link #fire()} method for firing 040 * events to the listeners. 041 * 042 * <p/> 043 * To use this class, suppose you want to support ActionEvents. You would do: 044 * <code><pre> 045 * public class MyActionEventSource 046 * { 047 * private EventListenerSupport<ActionListener> actionListeners = 048 * EventListenerSupport.create(ActionListener.class); 049 * 050 * public void someMethodThatFiresAction() 051 * { 052 * ActionEvent e = new ActionEvent(this, ActionEvent.ACTION_PERFORMED, "somethingCool"); 053 * actionListeners.fire().actionPerformed(e); 054 * } 055 * } 056 * </pre></code> 057 * 058 * Serializing an {@link EventListenerSupport} instance will result in any 059 * non-{@link Serializable} listeners being silently dropped. 060 * 061 * @param <L> the type of event listener that is supported by this proxy. 062 * 063 * @since 3.0 064 * @version $Id: EventListenerSupport.java 1082302 2011-03-16 21:08:27Z oheger $ 065 */ 066 public class EventListenerSupport<L> implements Serializable { 067 068 /** Serialization version */ 069 private static final long serialVersionUID = 3593265990380473632L; 070 071 /** 072 * The list used to hold the registered listeners. This list is 073 * intentionally a thread-safe copy-on-write-array so that traversals over 074 * the list of listeners will be atomic. 075 */ 076 private List<L> listeners = new CopyOnWriteArrayList<L>(); 077 078 /** 079 * The proxy representing the collection of listeners. Calls to this proxy 080 * object will sent to all registered listeners. 081 */ 082 private transient L proxy; 083 084 /** 085 * Empty typed array for #getListeners(). 086 */ 087 private transient L[] prototypeArray; 088 089 /** 090 * Creates an EventListenerSupport object which supports the specified 091 * listener type. 092 * 093 * @param <T> the type of the listener interface 094 * @param listenerInterface the type of listener interface that will receive 095 * events posted using this class. 096 * 097 * @return an EventListenerSupport object which supports the specified 098 * listener type. 099 * 100 * @throws NullPointerException if <code>listenerInterface</code> is 101 * <code>null</code>. 102 * @throws IllegalArgumentException if <code>listenerInterface</code> is 103 * not an interface. 104 */ 105 public static <T> EventListenerSupport<T> create(Class<T> listenerInterface) { 106 return new EventListenerSupport<T>(listenerInterface); 107 } 108 109 /** 110 * Creates an EventListenerSupport object which supports the provided 111 * listener interface. 112 * 113 * @param listenerInterface the type of listener interface that will receive 114 * events posted using this class. 115 * 116 * @throws NullPointerException if <code>listenerInterface</code> is 117 * <code>null</code>. 118 * @throws IllegalArgumentException if <code>listenerInterface</code> is 119 * not an interface. 120 */ 121 public EventListenerSupport(Class<L> listenerInterface) { 122 this(listenerInterface, Thread.currentThread().getContextClassLoader()); 123 } 124 125 /** 126 * Creates an EventListenerSupport object which supports the provided 127 * listener interface using the specified class loader to create the JDK 128 * dynamic proxy. 129 * 130 * @param listenerInterface the listener interface. 131 * @param classLoader the class loader. 132 * 133 * @throws NullPointerException if <code>listenerInterface</code> or 134 * <code>classLoader</code> is <code>null</code>. 135 * @throws IllegalArgumentException if <code>listenerInterface</code> is 136 * not an interface. 137 */ 138 public EventListenerSupport(Class<L> listenerInterface, ClassLoader classLoader) { 139 this(); 140 Validate.notNull(listenerInterface, "Listener interface cannot be null."); 141 Validate.notNull(classLoader, "ClassLoader cannot be null."); 142 Validate.isTrue(listenerInterface.isInterface(), "Class {0} is not an interface", 143 listenerInterface.getName()); 144 initializeTransientFields(listenerInterface, classLoader); 145 } 146 147 /** 148 * Create a new EventListenerSupport instance. 149 * Serialization-friendly constructor. 150 */ 151 private EventListenerSupport() { 152 } 153 154 /** 155 * Returns a proxy object which can be used to call listener methods on all 156 * of the registered event listeners. All calls made to this proxy will be 157 * forwarded to all registered listeners. 158 * 159 * @return a proxy object which can be used to call listener methods on all 160 * of the registered event listeners 161 */ 162 public L fire() { 163 return proxy; 164 } 165 166 //********************************************************************************************************************** 167 // Other Methods 168 //********************************************************************************************************************** 169 170 /** 171 * Registers an event listener. 172 * 173 * @param listener the event listener (may not be <code>null</code>). 174 * 175 * @throws NullPointerException if <code>listener</code> is 176 * <code>null</code>. 177 */ 178 public void addListener(L listener) { 179 Validate.notNull(listener, "Listener object cannot be null."); 180 listeners.add(listener); 181 } 182 183 /** 184 * Returns the number of registered listeners. 185 * 186 * @return the number of registered listeners. 187 */ 188 int getListenerCount() { 189 return listeners.size(); 190 } 191 192 /** 193 * Unregisters an event listener. 194 * 195 * @param listener the event listener (may not be <code>null</code>). 196 * 197 * @throws NullPointerException if <code>listener</code> is 198 * <code>null</code>. 199 */ 200 public void removeListener(L listener) { 201 Validate.notNull(listener, "Listener object cannot be null."); 202 listeners.remove(listener); 203 } 204 205 /** 206 * Get an array containing the currently registered listeners. 207 * Modification to this array's elements will have no effect on the 208 * {@link EventListenerSupport} instance. 209 * @return L[] 210 */ 211 public L[] getListeners() { 212 return listeners.toArray(prototypeArray); 213 } 214 215 /** 216 * Serialize. 217 * @param objectOutputStream the output stream 218 * @throws IOException if an IO error occurs 219 */ 220 private void writeObject(ObjectOutputStream objectOutputStream) throws IOException { 221 ArrayList<L> serializableListeners = new ArrayList<L>(); 222 223 // don't just rely on instanceof Serializable: 224 ObjectOutputStream testObjectOutputStream = new ObjectOutputStream(new ByteArrayOutputStream()); 225 for (L listener : listeners) { 226 try { 227 testObjectOutputStream.writeObject(listener); 228 serializableListeners.add(listener); 229 } catch (IOException exception) { 230 //recreate test stream in case of indeterminate state 231 testObjectOutputStream = new ObjectOutputStream(new ByteArrayOutputStream()); 232 } 233 } 234 /* 235 * we can reconstitute everything we need from an array of our listeners, 236 * which has the additional advantage of typically requiring less storage than a list: 237 */ 238 objectOutputStream.writeObject(serializableListeners.toArray(prototypeArray)); 239 } 240 241 /** 242 * Deserialize. 243 * @param objectInputStream the input stream 244 * @throws IOException if an IO error occurs 245 * @throws ClassNotFoundException if the class cannot be resolved 246 */ 247 private void readObject(ObjectInputStream objectInputStream) throws IOException, ClassNotFoundException { 248 @SuppressWarnings("unchecked") 249 L[] listeners = (L[]) objectInputStream.readObject(); 250 251 this.listeners = new CopyOnWriteArrayList<L>(listeners); 252 253 @SuppressWarnings("unchecked") 254 Class<L> listenerInterface = (Class<L>) listeners.getClass().getComponentType(); 255 256 initializeTransientFields(listenerInterface, Thread.currentThread().getContextClassLoader()); 257 } 258 259 /** 260 * Initialize transient fields. 261 * @param listenerInterface the class of the listener interface 262 * @param classLoader the class loader to be used 263 */ 264 private void initializeTransientFields(Class<L> listenerInterface, ClassLoader classLoader) { 265 @SuppressWarnings("unchecked") 266 L[] array = (L[]) Array.newInstance(listenerInterface, 0); 267 this.prototypeArray = array; 268 createProxy(listenerInterface, classLoader); 269 } 270 271 /** 272 * Create the proxy object. 273 * @param listenerInterface the class of the listener interface 274 * @param classLoader the class loader to be used 275 */ 276 private void createProxy(Class<L> listenerInterface, ClassLoader classLoader) { 277 proxy = listenerInterface.cast(Proxy.newProxyInstance(classLoader, 278 new Class[] { listenerInterface }, createInvocationHandler())); 279 } 280 281 /** 282 * Create the {@link InvocationHandler} responsible for broadcasting calls 283 * to the managed listeners. Subclasses can override to provide custom behavior. 284 * @return ProxyInvocationHandler 285 */ 286 protected InvocationHandler createInvocationHandler() { 287 return new ProxyInvocationHandler(); 288 } 289 290 /** 291 * An invocation handler used to dispatch the event(s) to all the listeners. 292 */ 293 protected class ProxyInvocationHandler implements InvocationHandler { 294 /** Serialization version */ 295 private static final long serialVersionUID = 1L; 296 297 /** 298 * Propagates the method call to all registered listeners in place of 299 * the proxy listener object. 300 * 301 * @param proxy the proxy object representing a listener on which the 302 * invocation was called. 303 * @param method the listener method that will be called on all of the 304 * listeners. 305 * @param args event arguments to propagate to the listeners. 306 * @return the result of the method call 307 * @throws Throwable if an error occurs 308 */ 309 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 310 for (L listener : listeners) { 311 method.invoke(listener, args); 312 } 313 return null; 314 } 315 } 316 }