EventListenerSupport.java

  1. /*
  2.  * Licensed to the Apache Software Foundation (ASF) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * The ASF licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *      http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */

  17. package org.apache.commons.lang3.event;

  18. import java.io.ByteArrayOutputStream;
  19. import java.io.IOException;
  20. import java.io.ObjectInputStream;
  21. import java.io.ObjectOutputStream;
  22. import java.io.Serializable;
  23. import java.lang.reflect.InvocationHandler;
  24. import java.lang.reflect.InvocationTargetException;
  25. import java.lang.reflect.Method;
  26. import java.lang.reflect.Proxy;
  27. import java.util.ArrayList;
  28. import java.util.List;
  29. import java.util.Objects;
  30. import java.util.concurrent.CopyOnWriteArrayList;

  31. import org.apache.commons.lang3.ArrayUtils;
  32. import org.apache.commons.lang3.Validate;
  33. import org.apache.commons.lang3.exception.ExceptionUtils;
  34. import org.apache.commons.lang3.function.FailableConsumer;

  35. /**
  36.  * An EventListenerSupport object can be used to manage a list of event
  37.  * listeners of a particular type. The class provides
  38.  * {@link #addListener(Object)} and {@link #removeListener(Object)} methods
  39.  * for registering listeners, as well as a {@link #fire()} method for firing
  40.  * events to the listeners.
  41.  *
  42.  * <p>
  43.  * To use this class, suppose you want to support ActionEvents.  You would do:
  44.  * </p>
  45.  * <pre>{@code
  46.  * public class MyActionEventSource
  47.  * {
  48.  *   private EventListenerSupport<ActionListener> actionListeners =
  49.  *       EventListenerSupport.create(ActionListener.class);
  50.  *
  51.  *   public void someMethodThatFiresAction()
  52.  *   {
  53.  *     ActionEvent e = new ActionEvent(this, ActionEvent.ACTION_PERFORMED, "somethingCool");
  54.  *     actionListeners.fire().actionPerformed(e);
  55.  *   }
  56.  * }
  57.  * }</pre>
  58.  *
  59.  * <p>
  60.  * Serializing an {@link EventListenerSupport} instance will result in any
  61.  * non-{@link Serializable} listeners being silently dropped.
  62.  * </p>
  63.  *
  64.  * @param <L> the type of event listener that is supported by this proxy.
  65.  *
  66.  * @since 3.0
  67.  */
  68. public class EventListenerSupport<L> implements Serializable {

  69.     /**
  70.      * An invocation handler used to dispatch the event(s) to all the listeners.
  71.      */
  72.     protected class ProxyInvocationHandler implements InvocationHandler {

  73.         private final FailableConsumer<Throwable, IllegalAccessException> handler;

  74.         /**
  75.          * Constructs a new instance.
  76.          */
  77.         public ProxyInvocationHandler() {
  78.             this(ExceptionUtils::rethrow);
  79.         }

  80.         /**
  81.          * Constructs a new instance.
  82.          *
  83.          * @param handler Handles Throwables.
  84.          * @since 3.15.0
  85.          */
  86.         public ProxyInvocationHandler(final FailableConsumer<Throwable, IllegalAccessException> handler) {
  87.             this.handler = Objects.requireNonNull(handler);
  88.         }

  89.         /**
  90.          * Handles an exception thrown by a listener. By default rethrows the given Throwable.
  91.          *
  92.          * @param t The Throwable
  93.          * @throws IllegalAccessException thrown by the listener.
  94.          * @throws IllegalArgumentException thrown by the listener.
  95.          * @throws InvocationTargetException thrown by the listener.
  96.          * @since 3.15.0
  97.          */
  98.         protected void handle(final Throwable t) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
  99.             handler.accept(t);
  100.         }

  101.         /**
  102.          * Propagates the method call to all registered listeners in place of the proxy listener object.
  103.          *
  104.          * @param unusedProxy the proxy object representing a listener on which the invocation was called; not used
  105.          * @param method the listener method that will be called on all of the listeners.
  106.          * @param args event arguments to propagate to the listeners.
  107.          * @return the result of the method call
  108.          * @throws InvocationTargetException if an error occurs
  109.          * @throws IllegalArgumentException if an error occurs
  110.          * @throws IllegalAccessException if an error occurs
  111.          */
  112.         @Override
  113.         public Object invoke(final Object unusedProxy, final Method method, final Object[] args)
  114.                 throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
  115.             for (final L listener : listeners) {
  116.                 try {
  117.                     method.invoke(listener, args);
  118.                 } catch (final Throwable t) {
  119.                     handle(t);
  120.                 }
  121.             }
  122.             return null;
  123.         }
  124.     }

  125.     /** Serialization version */
  126.     private static final long serialVersionUID = 3593265990380473632L;

  127.     /**
  128.      * Creates an EventListenerSupport object which supports the specified
  129.      * listener type.
  130.      *
  131.      * @param <T> the type of the listener interface
  132.      * @param listenerInterface the type of listener interface that will receive
  133.      *        events posted using this class.
  134.      *
  135.      * @return an EventListenerSupport object which supports the specified
  136.      *         listener type.
  137.      *
  138.      * @throws NullPointerException if {@code listenerInterface} is
  139.      *         {@code null}.
  140.      * @throws IllegalArgumentException if {@code listenerInterface} is
  141.      *         not an interface.
  142.      */
  143.     public static <T> EventListenerSupport<T> create(final Class<T> listenerInterface) {
  144.         return new EventListenerSupport<>(listenerInterface);
  145.     }

  146.     /**
  147.      * The list used to hold the registered listeners. This list is
  148.      * intentionally a thread-safe copy-on-write-array so that traversals over
  149.      * the list of listeners will be atomic.
  150.      */
  151.     private List<L> listeners = new CopyOnWriteArrayList<>();

  152.     /**
  153.      * The proxy representing the collection of listeners. Calls to this proxy
  154.      * object will be sent to all registered listeners.
  155.      */
  156.     private transient L proxy;

  157.     /**
  158.      * Empty typed array for #getListeners().
  159.      */
  160.     private transient L[] prototypeArray;

  161.     /**
  162.      * Constructs a new EventListenerSupport instance.
  163.      * Serialization-friendly constructor.
  164.      */
  165.     private EventListenerSupport() {
  166.     }

  167.     /**
  168.      * Creates an EventListenerSupport object which supports the provided
  169.      * listener interface.
  170.      *
  171.      * @param listenerInterface the type of listener interface that will receive
  172.      *        events posted using this class.
  173.      *
  174.      * @throws NullPointerException if {@code listenerInterface} is
  175.      *         {@code null}.
  176.      * @throws IllegalArgumentException if {@code listenerInterface} is
  177.      *         not an interface.
  178.      */
  179.     public EventListenerSupport(final Class<L> listenerInterface) {
  180.         this(listenerInterface, Thread.currentThread().getContextClassLoader());
  181.     }

  182.     /**
  183.      * Creates an EventListenerSupport object which supports the provided
  184.      * listener interface using the specified class loader to create the JDK
  185.      * dynamic proxy.
  186.      *
  187.      * @param listenerInterface the listener interface.
  188.      * @param classLoader       the class loader.
  189.      *
  190.      * @throws NullPointerException if {@code listenerInterface} or
  191.      *         {@code classLoader} is {@code null}.
  192.      * @throws IllegalArgumentException if {@code listenerInterface} is
  193.      *         not an interface.
  194.      */
  195.     public EventListenerSupport(final Class<L> listenerInterface, final ClassLoader classLoader) {
  196.         this();
  197.         Objects.requireNonNull(listenerInterface, "listenerInterface");
  198.         Objects.requireNonNull(classLoader, "classLoader");
  199.         Validate.isTrue(listenerInterface.isInterface(), "Class %s is not an interface",
  200.                 listenerInterface.getName());
  201.         initializeTransientFields(listenerInterface, classLoader);
  202.     }

  203.     /**
  204.      * Registers an event listener.
  205.      *
  206.      * @param listener the event listener (may not be {@code null}).
  207.      *
  208.      * @throws NullPointerException if {@code listener} is
  209.      *         {@code null}.
  210.      */
  211.     public void addListener(final L listener) {
  212.         addListener(listener, true);
  213.     }

  214.     /**
  215.      * Registers an event listener. Will not add a pre-existing listener
  216.      * object to the list if {@code allowDuplicate} is false.
  217.      *
  218.      * @param listener the event listener (may not be {@code null}).
  219.      * @param allowDuplicate the flag for determining if duplicate listener
  220.      * objects are allowed to be registered.
  221.      *
  222.      * @throws NullPointerException if {@code listener} is {@code null}.
  223.      * @since 3.5
  224.      */
  225.     public void addListener(final L listener, final boolean allowDuplicate) {
  226.         Objects.requireNonNull(listener, "listener");
  227.         if (allowDuplicate || !listeners.contains(listener)) {
  228.             listeners.add(listener);
  229.         }
  230.     }

  231.     /**
  232.      * Creates the {@link InvocationHandler} responsible for broadcasting calls
  233.      * to the managed listeners. Subclasses can override to provide custom behavior.
  234.      *
  235.      * @return ProxyInvocationHandler
  236.      */
  237.     protected InvocationHandler createInvocationHandler() {
  238.         return new ProxyInvocationHandler();
  239.     }

  240.     /**
  241.      * Creates the proxy object.
  242.      *
  243.      * @param listenerInterface the class of the listener interface
  244.      * @param classLoader the class loader to be used
  245.      */
  246.     private void createProxy(final Class<L> listenerInterface, final ClassLoader classLoader) {
  247.         proxy = listenerInterface.cast(Proxy.newProxyInstance(classLoader,
  248.                 new Class[] { listenerInterface }, createInvocationHandler()));
  249.     }

  250.     /**
  251.      * Returns a proxy object which can be used to call listener methods on all
  252.      * of the registered event listeners. All calls made to this proxy will be
  253.      * forwarded to all registered listeners.
  254.      *
  255.      * @return a proxy object which can be used to call listener methods on all
  256.      * of the registered event listeners
  257.      */
  258.     public L fire() {
  259.         return proxy;
  260.     }

  261.     /**
  262.      * Gets the number of registered listeners.
  263.      *
  264.      * @return the number of registered listeners.
  265.      */
  266.     int getListenerCount() {
  267.         return listeners.size();
  268.     }

  269.     /**
  270.      * Gets an array containing the currently registered listeners.
  271.      * Modification to this array's elements will have no effect on the
  272.      * {@link EventListenerSupport} instance.
  273.      * @return L[]
  274.      */
  275.     public L[] getListeners() {
  276.         return listeners.toArray(prototypeArray);
  277.     }

  278.     /**
  279.      * Initializes transient fields.
  280.      *
  281.      * @param listenerInterface the class of the listener interface
  282.      * @param classLoader the class loader to be used
  283.      */
  284.     private void initializeTransientFields(final Class<L> listenerInterface, final ClassLoader classLoader) {
  285.         // Will throw CCE here if not correct
  286.         this.prototypeArray = ArrayUtils.newInstance(listenerInterface, 0);
  287.         createProxy(listenerInterface, classLoader);
  288.     }

  289.     /**
  290.      * Deserializes.
  291.      *
  292.      * @param objectInputStream the input stream
  293.      * @throws IOException if an IO error occurs
  294.      * @throws ClassNotFoundException if the class cannot be resolved
  295.      */
  296.     private void readObject(final ObjectInputStream objectInputStream) throws IOException, ClassNotFoundException {
  297.         @SuppressWarnings("unchecked") // Will throw CCE here if not correct
  298.         final L[] srcListeners = (L[]) objectInputStream.readObject();
  299.         this.listeners = new CopyOnWriteArrayList<>(srcListeners);
  300.         final Class<L> listenerInterface = ArrayUtils.getComponentType(srcListeners);
  301.         initializeTransientFields(listenerInterface, Thread.currentThread().getContextClassLoader());
  302.     }

  303.     /**
  304.      * Unregisters an event listener.
  305.      *
  306.      * @param listener the event listener (may not be {@code null}).
  307.      *
  308.      * @throws NullPointerException if {@code listener} is
  309.      *         {@code null}.
  310.      */
  311.     public void removeListener(final L listener) {
  312.         Objects.requireNonNull(listener, "listener");
  313.         listeners.remove(listener);
  314.     }

  315.     /**
  316.      * Serializes.
  317.      *
  318.      * @param objectOutputStream the output stream
  319.      * @throws IOException if an IO error occurs
  320.      */
  321.     private void writeObject(final ObjectOutputStream objectOutputStream) throws IOException {
  322.         final ArrayList<L> serializableListeners = new ArrayList<>();
  323.         // don't just rely on instanceof Serializable:
  324.         ObjectOutputStream testObjectOutputStream = new ObjectOutputStream(new ByteArrayOutputStream());
  325.         for (final L listener : listeners) {
  326.             try {
  327.                 testObjectOutputStream.writeObject(listener);
  328.                 serializableListeners.add(listener);
  329.             } catch (final IOException exception) {
  330.                 //recreate test stream in case of indeterminate state
  331.                 testObjectOutputStream = new ObjectOutputStream(new ByteArrayOutputStream());
  332.             }
  333.         }
  334.         /*
  335.          * we can reconstitute everything we need from an array of our listeners,
  336.          * which has the additional advantage of typically requiring less storage than a list:
  337.          */
  338.         objectOutputStream.writeObject(serializableListeners.toArray(prototypeArray));
  339.     }
  340. }