add serialization support to EventListenerSupport

git-svn-id: https://svn.apache.org/repos/asf/commons/proper/lang/trunk@987326 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Matthew Jason Benson 2010-08-19 21:49:53 +00:00
parent d2a3a2b1c6
commit be6641bd99
2 changed files with 148 additions and 19 deletions

View File

@ -17,10 +17,16 @@
package org.apache.commons.lang3.event; 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.Array;
import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.lang.reflect.Proxy; import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArrayList;
@ -49,30 +55,36 @@
* } * }
* </pre></code> * </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. * @param <L> the type of event listener that is supported by this proxy.
* *
* @since 3.0 * @since 3.0
* @version $Id$ * @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 * The list used to hold the registered listeners. This list is
* intentionally a thread-safe copy-on-write-array so that traversals over * intentionally a thread-safe copy-on-write-array so that traversals over
* the list of listeners will be atomic. * the list of listeners will be atomic.
*/ */
private final List<L> listeners = new CopyOnWriteArrayList<L>(); private List<L> listeners = new CopyOnWriteArrayList<L>();
/** /**
* The proxy representing the collection of listeners. Calls to this proxy * The proxy representing the collection of listeners. Calls to this proxy
* object will sent to all registered listeners. * object will sent to all registered listeners.
*/ */
private final L proxy; private transient L proxy;
/** /**
* Empty typed array for #getListeners(). * Empty typed array for #getListeners().
*/ */
private final L[] prototypeArray; private transient L[] prototypeArray;
/** /**
* Creates an EventListenerSupport object which supports the specified * Creates an EventListenerSupport object which supports the specified
@ -126,17 +138,19 @@ public EventListenerSupport(Class<L> listenerInterface)
*/ */
public EventListenerSupport(Class<L> listenerInterface, ClassLoader classLoader) public EventListenerSupport(Class<L> listenerInterface, ClassLoader classLoader)
{ {
this();
Validate.notNull(listenerInterface, "Listener interface cannot be null."); Validate.notNull(listenerInterface, "Listener interface cannot be null.");
Validate.notNull(classLoader, "ClassLoader cannot be null."); Validate.notNull(classLoader, "ClassLoader cannot be null.");
Validate.isTrue(listenerInterface.isInterface(), Validate.isTrue(listenerInterface.isInterface(), "Class {0} is not an interface",
"Class {0} is not an interface", listenerInterface.getName());
listenerInterface.getName()); initializeTransientFields(listenerInterface, classLoader);
proxy = listenerInterface.cast(Proxy.newProxyInstance(classLoader, }
new Class[]{listenerInterface},
new ProxyInvocationHandler())); /**
@SuppressWarnings("unchecked") * Create a new EventListenerSupport instance.
final L[] prototypeArray = (L[]) Array.newInstance(listenerInterface, 0); * Serialization-friendly constructor.
this.prototypeArray = prototypeArray; */
private EventListenerSupport() {
} }
/** /**
@ -204,11 +218,81 @@ public L[] getListeners() {
return listeners.toArray(prototypeArray); return listeners.toArray(prototypeArray);
} }
/**
* 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. * An invocation handler used to dispatch the event(s) to all the listeners.
*/ */
private class ProxyInvocationHandler implements InvocationHandler private class ProxyInvocationHandler implements InvocationHandler
{ {
/** Serialization version */
private static final long serialVersionUID = 1L;
/** /**
* Propagates the method call to all registered listeners in place of * Propagates the method call to all registered listeners in place of
* the proxy listener object. * the proxy listener object.

View File

@ -21,6 +21,12 @@
import java.awt.event.ActionEvent; import java.awt.event.ActionEvent;
import java.awt.event.ActionListener; 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.ArrayList;
import java.util.List; import java.util.List;
@ -118,6 +124,7 @@ public void testGetListeners() {
ActionListener[] listeners = listenerSupport.getListeners(); ActionListener[] listeners = listenerSupport.getListeners();
assertEquals(0, listeners.length); assertEquals(0, listeners.length);
assertEquals(ActionListener.class, listeners.getClass().getComponentType());
ActionListener[] empty = listeners; ActionListener[] empty = listeners;
//for fun, show that the same empty instance is used //for fun, show that the same empty instance is used
assertSame(empty, listenerSupport.getListeners()); assertSame(empty, listenerSupport.getListeners());
@ -125,7 +132,6 @@ public void testGetListeners() {
ActionListener listener1 = EasyMock.createNiceMock(ActionListener.class); ActionListener listener1 = EasyMock.createNiceMock(ActionListener.class);
listenerSupport.addListener(listener1); listenerSupport.addListener(listener1);
assertEquals(1, listenerSupport.getListeners().length); assertEquals(1, listenerSupport.getListeners().length);
assertEquals(ActionListener.class, listenerSupport.getListeners().getClass().getComponentType());
ActionListener listener2 = EasyMock.createNiceMock(ActionListener.class); ActionListener listener2 = EasyMock.createNiceMock(ActionListener.class);
listenerSupport.addListener(listener2); listenerSupport.addListener(listener2);
assertEquals(2, listenerSupport.getListeners().length); assertEquals(2, listenerSupport.getListeners().length);
@ -135,6 +141,45 @@ public void testGetListeners() {
assertSame(empty, listenerSupport.getListeners()); 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) private void addDeregisterListener(final EventListenerSupport<ActionListener> listenerSupport)
{ {
listenerSupport.addListener(new ActionListener() listenerSupport.addListener(new ActionListener()