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 * 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 1533570 2013-10-18 17:40:28Z sebb $
065 */
066public 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(final 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(final 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(final Class<L> listenerInterface, final 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(final 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(final 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(final ObjectOutputStream objectOutputStream) throws IOException {
221        final ArrayList<L> serializableListeners = new ArrayList<L>();
222
223        // don't just rely on instanceof Serializable:
224        ObjectOutputStream testObjectOutputStream = new ObjectOutputStream(new ByteArrayOutputStream());
225        for (final L listener : listeners) {
226            try {
227                testObjectOutputStream.writeObject(listener);
228                serializableListeners.add(listener);
229            } catch (final 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(final ObjectInputStream objectInputStream) throws IOException, ClassNotFoundException {
248        @SuppressWarnings("unchecked") // Will throw CCE here if not correct
249        final
250        L[] srcListeners = (L[]) objectInputStream.readObject();
251
252        this.listeners = new CopyOnWriteArrayList<L>(srcListeners);
253
254        @SuppressWarnings("unchecked") // Will throw CCE here if not correct
255        final
256        Class<L> listenerInterface = (Class<L>) srcListeners.getClass().getComponentType();
257
258        initializeTransientFields(listenerInterface, Thread.currentThread().getContextClassLoader());
259    }
260
261    /**
262     * Initialize transient fields.
263     * @param listenerInterface the class of the listener interface
264     * @param classLoader the class loader to be used
265     */
266    private void initializeTransientFields(final Class<L> listenerInterface, final ClassLoader classLoader) {
267        @SuppressWarnings("unchecked") // Will throw CCE here if not correct
268        final
269        L[] array = (L[]) Array.newInstance(listenerInterface, 0);
270        this.prototypeArray = array;
271        createProxy(listenerInterface, classLoader);
272    }
273
274    /**
275     * Create the proxy object.
276     * @param listenerInterface the class of the listener interface
277     * @param classLoader the class loader to be used
278     */
279    private void createProxy(final Class<L> listenerInterface, final ClassLoader classLoader) {
280        proxy = listenerInterface.cast(Proxy.newProxyInstance(classLoader,
281                new Class[] { listenerInterface }, createInvocationHandler()));
282    }
283
284    /**
285     * Create the {@link InvocationHandler} responsible for broadcasting calls
286     * to the managed listeners.  Subclasses can override to provide custom behavior.
287     * @return ProxyInvocationHandler
288     */
289    protected InvocationHandler createInvocationHandler() {
290        return new ProxyInvocationHandler();
291    }
292
293    /**
294     * An invocation handler used to dispatch the event(s) to all the listeners.
295     */
296    protected class ProxyInvocationHandler implements InvocationHandler {
297
298        /**
299         * Propagates the method call to all registered listeners in place of
300         * the proxy listener object.
301         *
302         * @param unusedProxy the proxy object representing a listener on which the
303         *        invocation was called; not used
304         * @param method the listener method that will be called on all of the
305         *        listeners.
306         * @param args event arguments to propagate to the listeners.
307         * @return the result of the method call
308         * @throws Throwable if an error occurs
309         */
310        @Override
311        public Object invoke(final Object unusedProxy, final Method method, final Object[] args) throws Throwable {
312            for (final L listener : listeners) {
313                method.invoke(listener, args);
314            }
315            return null;
316        }
317    }
318}