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:
parent
d2a3a2b1c6
commit
be6641bd99
|
@ -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 @@
|
|||
* }
|
||||
* </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 EventListenerSupport(Class<L> listenerInterface)
|
|||
*/
|
||||
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() {
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -204,11 +218,81 @@ public L[] getListeners() {
|
|||
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.
|
||||
*/
|
||||
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.
|
||||
|
|
|
@ -21,6 +21,12 @@
|
|||
|
||||
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 void testGetListeners() {
|
|||
|
||||
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 void testGetListeners() {
|
|||
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 void testGetListeners() {
|
|||
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()
|
||||
|
|
Loading…
Reference in New Issue