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