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