commons-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From mben...@apache.org
Subject svn commit: r987326 - in /commons/proper/lang/trunk/src: main/java/org/apache/commons/lang3/event/EventListenerSupport.java test/java/org/apache/commons/lang3/event/EventListenerSupportTest.java
Date Thu, 19 Aug 2010 21:49:53 GMT
Author: mbenson
Date: Thu Aug 19 21:49:53 2010
New Revision: 987326

URL: http://svn.apache.org/viewvc?rev=987326&view=rev
Log:
add serialization support to EventListenerSupport

Modified:
    commons/proper/lang/trunk/src/main/java/org/apache/commons/lang3/event/EventListenerSupport.java
    commons/proper/lang/trunk/src/test/java/org/apache/commons/lang3/event/EventListenerSupportTest.java

Modified: commons/proper/lang/trunk/src/main/java/org/apache/commons/lang3/event/EventListenerSupport.java
URL: http://svn.apache.org/viewvc/commons/proper/lang/trunk/src/main/java/org/apache/commons/lang3/event/EventListenerSupport.java?rev=987326&r1=987325&r2=987326&view=diff
==============================================================================
--- commons/proper/lang/trunk/src/main/java/org/apache/commons/lang3/event/EventListenerSupport.java
(original)
+++ commons/proper/lang/trunk/src/main/java/org/apache/commons/lang3/event/EventListenerSupport.java
Thu Aug 19 21:49:53 2010
@@ -17,10 +17,16 @@
 
 package org.apache.commons.lang3.event;
 
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
 import java.lang.reflect.Array;
 import java.lang.reflect.InvocationHandler;
 import java.lang.reflect.Method;
 import java.lang.reflect.Proxy;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.CopyOnWriteArrayList;
 
@@ -49,30 +55,36 @@ import org.apache.commons.lang3.Validate
  * }
  * </pre></code>
  *
+ * Serializing an {@link EventListenerSupport} instance will result in any
+ * non-{@link Serializable} listeners being silently dropped.
+ *
  * @param <L> the type of event listener that is supported by this proxy.
  *
  * @since 3.0
  * @version $Id$
  */
-public class EventListenerSupport<L>
+public class EventListenerSupport<L> implements Serializable
 {
+    /** Serialization version */
+    private static final long serialVersionUID = 3593265990380473632L;
+
     /**
-    * The list used to hold the registered listeners. This list is 
-    * intentionally a thread-safe copy-on-write-array so that traversals over
-    * the list of listeners will be atomic.
-    */
-    private final List<L> listeners = new CopyOnWriteArrayList<L>();
-    
+     * The list used to hold the registered listeners. This list is 
+     * intentionally a thread-safe copy-on-write-array so that traversals over
+     * the list of listeners will be atomic.
+     */
+    private List<L> listeners = new CopyOnWriteArrayList<L>();
+
     /**
      * The proxy representing the collection of listeners. Calls to this proxy 
      * object will sent to all registered listeners.
      */
-    private final L proxy;
+    private transient L proxy;
 
     /**
      * Empty typed array for #getListeners().
      */
-    private final L[] prototypeArray;
+    private transient L[] prototypeArray;
 
     /**
      * Creates an EventListenerSupport object which supports the specified 
@@ -126,17 +138,19 @@ public class EventListenerSupport<L>
      */
     public EventListenerSupport(Class<L> listenerInterface, ClassLoader classLoader)
     {
+        this();
         Validate.notNull(listenerInterface, "Listener interface cannot be null.");
         Validate.notNull(classLoader, "ClassLoader cannot be null.");
-        Validate.isTrue(listenerInterface.isInterface(), 
-            "Class {0} is not an interface", 
-            listenerInterface.getName());
-        proxy = listenerInterface.cast(Proxy.newProxyInstance(classLoader, 
-                new Class[]{listenerInterface},
-                new ProxyInvocationHandler()));
-        @SuppressWarnings("unchecked")
-        final L[] prototypeArray = (L[]) Array.newInstance(listenerInterface, 0);
-        this.prototypeArray = prototypeArray;
+        Validate.isTrue(listenerInterface.isInterface(), "Class {0} is not an interface",
+                listenerInterface.getName());
+        initializeTransientFields(listenerInterface, classLoader);
+    }
+
+    /**
+     * Create a new EventListenerSupport instance.
+     * Serialization-friendly constructor.
+     */
+    private EventListenerSupport() {
     }
 
     /**
@@ -205,10 +219,80 @@ public class EventListenerSupport<L>
     }
 
     /**
+     * Create the proxy object.
+     * @param listenerInterface
+     * @param classLoader
+     */
+    private void createProxy(Class<L> listenerInterface, ClassLoader classLoader) {
+        proxy = listenerInterface.cast(Proxy.newProxyInstance(classLoader, 
+                new Class[]{listenerInterface},
+                new ProxyInvocationHandler()));
+    }
+
+    /**
+     * Serialize.
+     * @param objectOutputStream
+     * @throws IOException
+     */
+    private void writeObject(ObjectOutputStream objectOutputStream) throws IOException {
+        ArrayList<L> serializableListeners = new ArrayList<L>();
+
+        // don't just rely on instanceof Serializable:
+        ObjectOutputStream testObjectOutputStream = new ObjectOutputStream(new ByteArrayOutputStream());
+        for (L listener : listeners) {
+            try {
+                testObjectOutputStream.writeObject(listener);
+                serializableListeners.add(listener);
+            } catch (IOException exception) {
+                //recreate test stream in case of indeterminate state
+                testObjectOutputStream = new ObjectOutputStream(new ByteArrayOutputStream());
+            }
+        }
+        /*
+         * we can reconstitute everything we need from an array of our listeners,
+         * which has the additional advantage of typically requiring less storage than a
list:
+         */
+        objectOutputStream.writeObject(serializableListeners.toArray(prototypeArray));
+    }
+
+    /**
+     * Deserialize.
+     * @param objectInputStream
+     * @throws IOException
+     * @throws ClassNotFoundException
+     */
+    private void readObject(ObjectInputStream objectInputStream) throws IOException, ClassNotFoundException
{
+        @SuppressWarnings("unchecked")
+        L[] listeners = (L[]) objectInputStream.readObject();
+
+        this.listeners = new CopyOnWriteArrayList<L>(listeners);
+
+        @SuppressWarnings("unchecked")
+        Class<L> listenerInterface = (Class<L>) listeners.getClass().getComponentType();
+
+        initializeTransientFields(listenerInterface, Thread.currentThread().getContextClassLoader());
+    }
+
+    /**
+     * Initialize transient fields.
+     * @param listenerInterface
+     * @param classLoader
+     */
+    private void initializeTransientFields(Class<L> listenerInterface, ClassLoader
classLoader) {
+        createProxy(listenerInterface, classLoader);
+        @SuppressWarnings("unchecked")
+        L[] array = (L[]) Array.newInstance(listenerInterface, 0);
+        this.prototypeArray = array;
+    }
+
+    /**
      * An invocation handler used to dispatch the event(s) to all the listeners.
      */
     private class ProxyInvocationHandler implements InvocationHandler
     {
+        /** Serialization version */
+        private static final long serialVersionUID = 1L;
+
         /**
          * Propagates the method call to all registered listeners in place of
          * the proxy listener object.

Modified: commons/proper/lang/trunk/src/test/java/org/apache/commons/lang3/event/EventListenerSupportTest.java
URL: http://svn.apache.org/viewvc/commons/proper/lang/trunk/src/test/java/org/apache/commons/lang3/event/EventListenerSupportTest.java?rev=987326&r1=987325&r2=987326&view=diff
==============================================================================
--- commons/proper/lang/trunk/src/test/java/org/apache/commons/lang3/event/EventListenerSupportTest.java
(original)
+++ commons/proper/lang/trunk/src/test/java/org/apache/commons/lang3/event/EventListenerSupportTest.java
Thu Aug 19 21:49:53 2010
@@ -21,6 +21,12 @@ import junit.framework.TestCase;
 
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -118,6 +124,7 @@ public class EventListenerSupportTest ex
 
         ActionListener[] listeners = listenerSupport.getListeners();
         assertEquals(0, listeners.length);
+        assertEquals(ActionListener.class, listeners.getClass().getComponentType());
         ActionListener[] empty = listeners;
         //for fun, show that the same empty instance is used 
         assertSame(empty, listenerSupport.getListeners());
@@ -125,7 +132,6 @@ public class EventListenerSupportTest ex
         ActionListener listener1 = EasyMock.createNiceMock(ActionListener.class);
         listenerSupport.addListener(listener1);
         assertEquals(1, listenerSupport.getListeners().length);
-        assertEquals(ActionListener.class, listenerSupport.getListeners().getClass().getComponentType());
         ActionListener listener2 = EasyMock.createNiceMock(ActionListener.class);
         listenerSupport.addListener(listener2);
         assertEquals(2, listenerSupport.getListeners().length);
@@ -135,6 +141,45 @@ public class EventListenerSupportTest ex
         assertSame(empty, listenerSupport.getListeners());
     }
 
+    public void testSerialization() throws IOException, ClassNotFoundException {
+        EventListenerSupport<ActionListener> listenerSupport = EventListenerSupport.create(ActionListener.class);
+        listenerSupport.addListener(new ActionListener() {
+            
+            public void actionPerformed(ActionEvent e) {
+            }
+        });
+        listenerSupport.addListener(EasyMock.createNiceMock(ActionListener.class));
+
+        //serialize:
+        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+        ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
+
+        objectOutputStream.writeObject(listenerSupport);
+        objectOutputStream.close();
+
+        //deserialize:
+        @SuppressWarnings("unchecked")
+        EventListenerSupport<ActionListener> deserializedListenerSupport = (EventListenerSupport<ActionListener>)
new ObjectInputStream(
+                new ByteArrayInputStream(outputStream.toByteArray())).readObject();
+
+        //make sure we get a listener array back, of the correct component type, and that
it contains only the serializable mock
+        ActionListener[] listeners = deserializedListenerSupport.getListeners();
+        assertEquals(ActionListener.class, listeners.getClass().getComponentType());
+        assertEquals(1, listeners.length);
+
+        //now verify that the mock still receives events; we can infer that the proxy was
correctly reconstituted
+        ActionListener listener = listeners[0];
+        ActionEvent evt = new ActionEvent(new Object(), 666, "sit");
+        listener.actionPerformed(evt);
+        EasyMock.replay(listener);
+        deserializedListenerSupport.fire().actionPerformed(evt);
+        EasyMock.verify(listener);
+
+        //remove listener and verify we get an empty array of listeners
+        deserializedListenerSupport.removeListener(listener);
+        assertEquals(0, deserializedListenerSupport.getListeners().length);
+    }
+
     private void addDeregisterListener(final EventListenerSupport<ActionListener> listenerSupport)
     {
         listenerSupport.addListener(new ActionListener()



Mime
View raw message