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()
|