Coverage Report - org.apache.commons.lang3.event.EventListenerSupport
 
Classes in this File Line Coverage Branch Coverage Complexity
EventListenerSupport
100%
45/45
100%
2/2
1.2
EventListenerSupport$ProxyInvocationHandler
100%
5/5
100%
2/2
1.2
 
 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  
 
 18  
 package org.apache.commons.lang3.event;
 19  
 
 20  
 import java.io.ByteArrayOutputStream;
 21  
 import java.io.IOException;
 22  
 import java.io.ObjectInputStream;
 23  
 import java.io.ObjectOutputStream;
 24  
 import java.io.Serializable;
 25  
 import java.lang.reflect.Array;
 26  
 import java.lang.reflect.InvocationHandler;
 27  
 import java.lang.reflect.Method;
 28  
 import java.lang.reflect.Proxy;
 29  
 import java.util.ArrayList;
 30  
 import java.util.List;
 31  
 import java.util.concurrent.CopyOnWriteArrayList;
 32  
 
 33  
 import org.apache.commons.lang3.Validate;
 34  
 
 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  
  * <code><pre>
 45  
  * public class MyActionEventSource
 46  
  * {
 47  
  *   private EventListenerSupport<ActionListener> actionListeners =
 48  
  *       EventListenerSupport.create(ActionListener.class);
 49  
  *
 50  
  *   public void someMethodThatFiresAction()
 51  
  *   {
 52  
  *     ActionEvent e = new ActionEvent(this, ActionEvent.ACTION_PERFORMED, "somethingCool");
 53  
  *     actionListeners.fire().actionPerformed(e);
 54  
  *   }
 55  
  * }
 56  
  * </pre></code>
 57  
  *
 58  
  * Serializing an {@link EventListenerSupport} instance will result in any
 59  
  * non-{@link Serializable} listeners being silently dropped.
 60  
  *
 61  
  * @param <L> the type of event listener that is supported by this proxy.
 62  
  *
 63  
  * @since 3.0
 64  
  * @version $Id: EventListenerSupport.java 1436770 2013-01-22 07:09:45Z ggregory $
 65  
  */
 66  9
 public class EventListenerSupport<L> implements Serializable {
 67  
 
 68  
     /** Serialization version */
 69  
     private static final long serialVersionUID = 3593265990380473632L;
 70  
 
 71  
     /**
 72  
      * The list used to hold the registered listeners. This list is
 73  
      * intentionally a thread-safe copy-on-write-array so that traversals over
 74  
      * the list of listeners will be atomic.
 75  
      */
 76  15
     private List<L> listeners = new CopyOnWriteArrayList<L>();
 77  
 
 78  
     /**
 79  
      * The proxy representing the collection of listeners. Calls to this proxy
 80  
      * object will sent to all registered listeners.
 81  
      */
 82  
     private transient L proxy;
 83  
 
 84  
     /**
 85  
      * Empty typed array for #getListeners().
 86  
      */
 87  
     private transient L[] prototypeArray;
 88  
 
 89  
     /**
 90  
      * Creates an EventListenerSupport object which supports the specified
 91  
      * listener type.
 92  
      *
 93  
      * @param <T> the type of the listener interface
 94  
      * @param listenerInterface the type of listener interface that will receive
 95  
      *        events posted using this class.
 96  
      *
 97  
      * @return an EventListenerSupport object which supports the specified
 98  
      *         listener type.
 99  
      *
 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  14
         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  15
         this(listenerInterface, Thread.currentThread().getContextClassLoader());
 123  13
     }
 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  15
         this();
 140  15
         Validate.notNull(listenerInterface, "Listener interface cannot be null.");
 141  14
         Validate.notNull(classLoader, "ClassLoader cannot be null.");
 142  14
         Validate.isTrue(listenerInterface.isInterface(), "Class {0} is not an interface",
 143  
                 listenerInterface.getName());
 144  13
         initializeTransientFields(listenerInterface, classLoader);
 145  13
     }
 146  
 
 147  
     /**
 148  
      * Create a new EventListenerSupport instance.
 149  
      * Serialization-friendly constructor.
 150  
      */
 151  15
     private EventListenerSupport() {
 152  15
     }
 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  10
         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  22
         Validate.notNull(listener, "Listener object cannot be null.");
 180  21
         listeners.add(listener);
 181  21
     }
 182  
 
 183  
     /**
 184  
      * Returns the number of registered listeners.
 185  
      *
 186  
      * @return the number of registered listeners.
 187  
      */
 188  
     int getListenerCount() {
 189  2
         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  14
         Validate.notNull(listener, "Listener object cannot be null.");
 202  13
         listeners.remove(listener);
 203  13
     }
 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  8
         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  1
         final ArrayList<L> serializableListeners = new ArrayList<L>();
 222  
 
 223  
         // don't just rely on instanceof Serializable:
 224  1
         ObjectOutputStream testObjectOutputStream = new ObjectOutputStream(new ByteArrayOutputStream());
 225  1
         for (final L listener : listeners) {
 226  
             try {
 227  2
                 testObjectOutputStream.writeObject(listener);
 228  1
                 serializableListeners.add(listener);
 229  1
             } catch (final IOException exception) {
 230  
                 //recreate test stream in case of indeterminate state
 231  1
                 testObjectOutputStream = new ObjectOutputStream(new ByteArrayOutputStream());
 232  1
             }
 233  2
         }
 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  1
         objectOutputStream.writeObject(serializableListeners.toArray(prototypeArray));
 239  1
     }
 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  1
         L[] listeners = (L[]) objectInputStream.readObject();
 251  
 
 252  1
         this.listeners = new CopyOnWriteArrayList<L>(listeners);
 253  
 
 254  
         @SuppressWarnings("unchecked") // Will throw CCE here if not correct
 255  
         final
 256  1
         Class<L> listenerInterface = (Class<L>) listeners.getClass().getComponentType();
 257  
 
 258  1
         initializeTransientFields(listenerInterface, Thread.currentThread().getContextClassLoader());
 259  1
     }
 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  14
         L[] array = (L[]) Array.newInstance(listenerInterface, 0);
 270  14
         this.prototypeArray = array;
 271  14
         createProxy(listenerInterface, classLoader);
 272  14
     }
 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  14
         proxy = listenerInterface.cast(Proxy.newProxyInstance(classLoader,
 281  
                 new Class[] { listenerInterface }, createInvocationHandler()));
 282  14
     }
 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  13
         return new ProxyInvocationHandler();
 291  
     }
 292  
 
 293  
     /**
 294  
      * An invocation handler used to dispatch the event(s) to all the listeners.
 295  
      */
 296  14
     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 proxy the proxy object representing a listener on which the
 303  
          *        invocation was called.
 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 proxy, final Method method, final Object[] args) throws Throwable {
 312  9
             for (final L listener : listeners) {
 313  19
                 method.invoke(listener, args);
 314  19
             }
 315  9
             return null;
 316  
         }
 317  
     }
 318  
 }