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<>();
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} is
104     *         {@code null}.
105     * @throws IllegalArgumentException if {@code listenerInterface} is
106     *         not an interface.
107     */
108    public static <T> EventListenerSupport<T> create(final Class<T> listenerInterface) {
109        return new EventListenerSupport<>(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} is
120     *         {@code null}.
121     * @throws IllegalArgumentException if {@code listenerInterface} 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} or
137     *         {@code classLoader} is {@code null}.
138     * @throws IllegalArgumentException if {@code listenerInterface} 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 %s 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}).
177     *
178     * @throws NullPointerException if {@code listener} is
179     *         {@code null}.
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} is false.
188     *
189     * @param listener the event listener (may not be {@code null}).
190     * @param allowDuplicate the flag for determining if duplicate listener
191     * objects are allowed to be registered.
192     *
193     * @throws NullPointerException if {@code listener} is {@code null}.
194     * @since 3.5
195     */
196    public void addListener(final L listener, final boolean allowDuplicate) {
197        Validate.notNull(listener, "Listener object cannot be null.");
198        if (allowDuplicate || !listeners.contains(listener)) {
199            listeners.add(listener);
200        }
201    }
202
203    /**
204     * Returns the number of registered listeners.
205     *
206     * @return the number of registered listeners.
207     */
208    int getListenerCount() {
209        return listeners.size();
210    }
211
212    /**
213     * Unregisters an event listener.
214     *
215     * @param listener the event listener (may not be {@code null}).
216     *
217     * @throws NullPointerException if {@code listener} is
218     *         {@code null}.
219     */
220    public void removeListener(final L listener) {
221        Validate.notNull(listener, "Listener object cannot be null.");
222        listeners.remove(listener);
223    }
224
225    /**
226     * Gets an array containing the currently registered listeners.
227     * Modification to this array's elements will have no effect on the
228     * {@link EventListenerSupport} instance.
229     * @return L[]
230     */
231    public L[] getListeners() {
232        return listeners.toArray(prototypeArray);
233    }
234
235    /**
236     * Serialize.
237     * @param objectOutputStream the output stream
238     * @throws IOException if an IO error occurs
239     */
240    private void writeObject(final ObjectOutputStream objectOutputStream) throws IOException {
241        final ArrayList<L> serializableListeners = new ArrayList<>();
242
243        // don't just rely on instanceof Serializable:
244        ObjectOutputStream testObjectOutputStream = new ObjectOutputStream(new ByteArrayOutputStream());
245        for (final L listener : listeners) {
246            try {
247                testObjectOutputStream.writeObject(listener);
248                serializableListeners.add(listener);
249            } catch (final IOException exception) {
250                //recreate test stream in case of indeterminate state
251                testObjectOutputStream = new ObjectOutputStream(new ByteArrayOutputStream());
252            }
253        }
254        /*
255         * we can reconstitute everything we need from an array of our listeners,
256         * which has the additional advantage of typically requiring less storage than a list:
257         */
258        objectOutputStream.writeObject(serializableListeners.toArray(prototypeArray));
259    }
260
261    /**
262     * Deserialize.
263     * @param objectInputStream the input stream
264     * @throws IOException if an IO error occurs
265     * @throws ClassNotFoundException if the class cannot be resolved
266     */
267    private void readObject(final ObjectInputStream objectInputStream) throws IOException, ClassNotFoundException {
268        @SuppressWarnings("unchecked") // Will throw CCE here if not correct
269        final
270        L[] srcListeners = (L[]) objectInputStream.readObject();
271
272        this.listeners = new CopyOnWriteArrayList<>(srcListeners);
273
274        @SuppressWarnings("unchecked") // Will throw CCE here if not correct
275        final
276        Class<L> listenerInterface = (Class<L>) srcListeners.getClass().getComponentType();
277
278        initializeTransientFields(listenerInterface, Thread.currentThread().getContextClassLoader());
279    }
280
281    /**
282     * Initialize transient fields.
283     * @param listenerInterface the class of the listener interface
284     * @param classLoader the class loader to be used
285     */
286    private void initializeTransientFields(final Class<L> listenerInterface, final ClassLoader classLoader) {
287        @SuppressWarnings("unchecked") // Will throw CCE here if not correct
288        final
289        L[] array = (L[]) Array.newInstance(listenerInterface, 0);
290        this.prototypeArray = array;
291        createProxy(listenerInterface, classLoader);
292    }
293
294    /**
295     * Create the proxy object.
296     * @param listenerInterface the class of the listener interface
297     * @param classLoader the class loader to be used
298     */
299    private void createProxy(final Class<L> listenerInterface, final ClassLoader classLoader) {
300        proxy = listenerInterface.cast(Proxy.newProxyInstance(classLoader,
301                new Class[] { listenerInterface }, createInvocationHandler()));
302    }
303
304    /**
305     * Create the {@link InvocationHandler} responsible for broadcasting calls
306     * to the managed listeners.  Subclasses can override to provide custom behavior.
307     * @return ProxyInvocationHandler
308     */
309    protected InvocationHandler createInvocationHandler() {
310        return new ProxyInvocationHandler();
311    }
312
313    /**
314     * An invocation handler used to dispatch the event(s) to all the listeners.
315     */
316    protected class ProxyInvocationHandler implements InvocationHandler {
317
318        /**
319         * Propagates the method call to all registered listeners in place of
320         * the proxy listener object.
321         *
322         * @param unusedProxy the proxy object representing a listener on which the
323         *        invocation was called; not used
324         * @param method the listener method that will be called on all of the
325         *        listeners.
326         * @param args event arguments to propagate to the listeners.
327         * @return the result of the method call
328         * @throws Throwable if an error occurs
329         */
330        @Override
331        public Object invoke(final Object unusedProxy, final Method method, final Object[] args) throws Throwable {
332            for (final L listener : listeners) {
333                method.invoke(listener, args);
334            }
335            return null;
336        }
337    }
338}