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