Misc. event utils.
git-svn-id: https://svn.apache.org/repos/asf/commons/proper/lang/trunk@966589 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
427ec8621a
commit
6ef1437ef4
|
@ -0,0 +1,143 @@
|
||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
|
* contributor license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright ownership.
|
||||||
|
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||||
|
* (the "License"); you may not use this file except in compliance with
|
||||||
|
* the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.apache.commons.lang3.event;
|
||||||
|
|
||||||
|
import java.lang.reflect.InvocationHandler;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.lang.reflect.Proxy;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An EventListenerSupport object can be used to manage a list of event listeners of a particular type.
|
||||||
|
* <p/>
|
||||||
|
* To use this class, suppose you want to support ActionEvents. You would do:
|
||||||
|
* <pre>
|
||||||
|
* public class MyActionEventSource
|
||||||
|
* {
|
||||||
|
* private EventListenerSupport<ActionListener> actionListeners = EventListenerSupport.create(ActionListener.class);
|
||||||
|
* <p/>
|
||||||
|
* public void someMethodThatFiresAction()
|
||||||
|
* {
|
||||||
|
* ActionEvent e = new ActionEvent(this, ActionEvent.ACTION_PERFORMED, "somethingCool");
|
||||||
|
* actionListeners.getProxy().actionPerformed(e);
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* @param <L> The event listener type
|
||||||
|
*/
|
||||||
|
public class EventListenerSupport<L>
|
||||||
|
{
|
||||||
|
private final List<L> listeners;
|
||||||
|
private final L proxy;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an EventListenerSupport object which supports the specified listener type.
|
||||||
|
*
|
||||||
|
* @param listenerType the listener type
|
||||||
|
* @return an EventListenerSupport object which supports the specified listener type
|
||||||
|
*/
|
||||||
|
public static <T> EventListenerSupport<T> create(Class<T> listenerType)
|
||||||
|
{
|
||||||
|
return new EventListenerSupport<T>(listenerType);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an EventListenerSupport object which supports the provided listener interface.
|
||||||
|
*
|
||||||
|
* @param listenerInterface the listener interface
|
||||||
|
*/
|
||||||
|
public EventListenerSupport(Class<L> listenerInterface)
|
||||||
|
{
|
||||||
|
this(listenerInterface, Thread.currentThread().getContextClassLoader());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an EventListenerSupport object which supports the provided listener interface using the specified
|
||||||
|
* class loader to create the JDK dynamic proxy.
|
||||||
|
*
|
||||||
|
* @param listenerInterface the listener interface
|
||||||
|
* @param classLoader the class loader
|
||||||
|
*/
|
||||||
|
public EventListenerSupport(Class<L> listenerInterface, ClassLoader classLoader)
|
||||||
|
{
|
||||||
|
listeners = new CopyOnWriteArrayList<L>();
|
||||||
|
proxy = listenerInterface.cast(Proxy.newProxyInstance(classLoader, new Class[]{listenerInterface},
|
||||||
|
new ProxyInvocationHandler()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a proxy object which can be used to call listener methods on all of the registered event listeners.
|
||||||
|
*
|
||||||
|
* @return a proxy object which can be used to call listener methods on all of the registered event listeners
|
||||||
|
*/
|
||||||
|
public L fire()
|
||||||
|
{
|
||||||
|
return proxy;
|
||||||
|
}
|
||||||
|
|
||||||
|
//**********************************************************************************************************************
|
||||||
|
// Other Methods
|
||||||
|
//**********************************************************************************************************************
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers an event listener.
|
||||||
|
*
|
||||||
|
* @param listener the event listener
|
||||||
|
*/
|
||||||
|
public void addListener(L listener)
|
||||||
|
{
|
||||||
|
listeners.add(0, listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the number of registered listeners.
|
||||||
|
*
|
||||||
|
* @return the number of registered listeners
|
||||||
|
*/
|
||||||
|
public int getListenerCount()
|
||||||
|
{
|
||||||
|
return listeners.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unregisters an event listener.
|
||||||
|
*
|
||||||
|
* @param listener the event listener
|
||||||
|
*/
|
||||||
|
public void removeListener(L listener)
|
||||||
|
{
|
||||||
|
listeners.remove(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An invocation handler used to dispatch the event(s) to all the listeners.
|
||||||
|
*/
|
||||||
|
private class ProxyInvocationHandler implements InvocationHandler
|
||||||
|
{
|
||||||
|
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
|
||||||
|
{
|
||||||
|
for (int i = listeners.size() - 1; i >= 0; --i)
|
||||||
|
{
|
||||||
|
method.invoke(listeners.get(i), args);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,102 @@
|
||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
|
* contributor license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright ownership.
|
||||||
|
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||||
|
* (the "License"); you may not use this file except in compliance with
|
||||||
|
* the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.apache.commons.lang3.event;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.reflect.MethodUtils;
|
||||||
|
|
||||||
|
import java.lang.reflect.InvocationHandler;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.lang.reflect.Proxy;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public class EventUtils
|
||||||
|
{
|
||||||
|
public static <L> void addEventListener(Object eventSource, Class<L> listenerType, L listener)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
MethodUtils.invokeMethod(eventSource, "add" + listenerType.getSimpleName(), listener);
|
||||||
|
}
|
||||||
|
catch (NoSuchMethodException e)
|
||||||
|
{
|
||||||
|
throw new IllegalArgumentException("Class " + eventSource.getClass() + " does not have an accesible add" + listenerType.getSimpleName() + " method which takes a parameter of type " + listenerType.getClass().getName() + ".");
|
||||||
|
}
|
||||||
|
catch (IllegalAccessException e)
|
||||||
|
{
|
||||||
|
throw new IllegalArgumentException("Class " + eventSource.getClass() + " does not have an accesible add" + listenerType.getSimpleName () + " method which takes a parameter of type " + listenerType.getClass().getName() + ".");
|
||||||
|
}
|
||||||
|
catch (InvocationTargetException e)
|
||||||
|
{
|
||||||
|
throw new RuntimeException("Unable to add listener.", e.getCause());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Binds an event listener to a specific method on a specific object.
|
||||||
|
*
|
||||||
|
* @param target the target object
|
||||||
|
* @param methodName the name of the method to be called
|
||||||
|
* @param eventSource the object which is generating events (JButton, JList, etc.)
|
||||||
|
* @param listenerType the listener interface (ActionListener.class, SelectionListener.class, etc.)
|
||||||
|
* @param eventTypes the event types (method names) from the listener interface (if none specified, all will be
|
||||||
|
* supported)
|
||||||
|
*/
|
||||||
|
public static void bindEventsToMethod(Object target, String methodName, Object eventSource, Class listenerType, String... eventTypes)
|
||||||
|
{
|
||||||
|
final Object listener = Proxy.newProxyInstance(target.getClass().getClassLoader(), new Class[] { listenerType }, new EventBindingInvocationHandler(target, methodName, eventTypes));
|
||||||
|
addEventListener(eventSource, listenerType, listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class EventBindingInvocationHandler implements InvocationHandler
|
||||||
|
{
|
||||||
|
private final Object target;
|
||||||
|
private final String methodName;
|
||||||
|
private final Set<String> eventTypes;
|
||||||
|
|
||||||
|
public EventBindingInvocationHandler(final Object target, final String methodName, String[] eventTypes)
|
||||||
|
{
|
||||||
|
this.target = target;
|
||||||
|
this.methodName = methodName;
|
||||||
|
this.eventTypes = new HashSet<String>(Arrays.asList(eventTypes));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object invoke(final Object proxy, final Method method, final Object[] parameters) throws Throwable
|
||||||
|
{
|
||||||
|
if ( eventTypes.isEmpty() || eventTypes.contains(method.getName()))
|
||||||
|
{
|
||||||
|
if (hasMatchingParametersMethod(method))
|
||||||
|
{
|
||||||
|
return MethodUtils.invokeMethod(target, methodName, parameters);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return MethodUtils.invokeMethod(target, methodName, new Object[]{});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean hasMatchingParametersMethod(final Method method)
|
||||||
|
{
|
||||||
|
return MethodUtils.getAccessibleMethod(target.getClass(), methodName, method.getParameterTypes()) != null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,77 @@
|
||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
|
* contributor license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright ownership.
|
||||||
|
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||||
|
* (the "License"); you may not use this file except in compliance with
|
||||||
|
* the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.apache.commons.lang3.event;
|
||||||
|
|
||||||
|
import junit.framework.TestCase;
|
||||||
|
|
||||||
|
import java.awt.event.ActionEvent;
|
||||||
|
import java.awt.event.ActionListener;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class EventListenerSupportTest extends TestCase
|
||||||
|
{
|
||||||
|
public void testEventDispatchOrder()
|
||||||
|
{
|
||||||
|
EventListenerSupport<ActionListener> listenerSupport = EventListenerSupport.create(ActionListener.class);
|
||||||
|
final List<ActionListener> calledListeners = new ArrayList<ActionListener>();
|
||||||
|
|
||||||
|
final ActionListener listener1 = createListener(calledListeners);
|
||||||
|
final ActionListener listener2 = createListener(calledListeners);
|
||||||
|
listenerSupport.addListener(listener1);
|
||||||
|
listenerSupport.addListener(listener2);
|
||||||
|
listenerSupport.fire().actionPerformed(new ActionEvent("Hello", 0, "Hello"));
|
||||||
|
assertEquals(calledListeners.size(), 2);
|
||||||
|
assertSame(calledListeners.get(0), listener1);
|
||||||
|
assertSame(calledListeners.get(1), listener2);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testRemoveListenerDuringEvent()
|
||||||
|
{
|
||||||
|
final EventListenerSupport<ActionListener> listenerSupport = EventListenerSupport.create(ActionListener.class);
|
||||||
|
for (int i = 0; i < 10; ++i)
|
||||||
|
{
|
||||||
|
addDeregisterListener(listenerSupport);
|
||||||
|
}
|
||||||
|
assertEquals(listenerSupport.getListenerCount(), 10);
|
||||||
|
listenerSupport.fire().actionPerformed(new ActionEvent("Hello", 0, "Hello"));
|
||||||
|
assertEquals(listenerSupport.getListenerCount(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addDeregisterListener(final EventListenerSupport<ActionListener> listenerSupport)
|
||||||
|
{
|
||||||
|
listenerSupport.addListener(new ActionListener()
|
||||||
|
{
|
||||||
|
public void actionPerformed(ActionEvent e)
|
||||||
|
{
|
||||||
|
listenerSupport.removeListener(this);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private ActionListener createListener(final List<ActionListener> calledListeners)
|
||||||
|
{
|
||||||
|
return new ActionListener()
|
||||||
|
{
|
||||||
|
public void actionPerformed(ActionEvent e)
|
||||||
|
{
|
||||||
|
calledListeners.add(this);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,123 @@
|
||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
|
* contributor license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright ownership.
|
||||||
|
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||||
|
* (the "License"); you may not use this file except in compliance with
|
||||||
|
* the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.apache.commons.lang3.event;
|
||||||
|
|
||||||
|
import junit.framework.TestCase;
|
||||||
|
|
||||||
|
import java.beans.PropertyChangeEvent;
|
||||||
|
import java.beans.PropertyChangeListener;
|
||||||
|
import java.lang.reflect.InvocationHandler;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.lang.reflect.Proxy;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.TreeMap;
|
||||||
|
|
||||||
|
public class EventUtilsTest extends TestCase
|
||||||
|
{
|
||||||
|
public void testAddEventListener()
|
||||||
|
{
|
||||||
|
final PropertyChangeSource src = new PropertyChangeSource();
|
||||||
|
EventCountingInvociationHandler handler = new EventCountingInvociationHandler();
|
||||||
|
PropertyChangeListener listener = handler.createListener(PropertyChangeListener.class);
|
||||||
|
assertEquals(0, handler.getEventCount("propertyChange"));
|
||||||
|
EventUtils.addEventListener(src, PropertyChangeListener.class, listener);
|
||||||
|
assertEquals(0, handler.getEventCount("propertyChange"));
|
||||||
|
src.setProperty("newValue");
|
||||||
|
assertEquals(1, handler.getEventCount("propertyChange"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testBindEventsToMethod()
|
||||||
|
{
|
||||||
|
final PropertyChangeSource src = new PropertyChangeSource();
|
||||||
|
final EventCounter counter = new EventCounter();
|
||||||
|
EventUtils.bindEventsToMethod(counter, "eventOccurred", src, PropertyChangeListener.class);
|
||||||
|
assertEquals(0, counter.getCount());
|
||||||
|
src.setProperty("newValue");
|
||||||
|
assertEquals(1, counter.getCount());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class EventCounter
|
||||||
|
{
|
||||||
|
private int count;
|
||||||
|
|
||||||
|
public void eventOccurred()
|
||||||
|
{
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCount()
|
||||||
|
{
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class EventCountingInvociationHandler implements InvocationHandler
|
||||||
|
{
|
||||||
|
private Map<String, Integer> eventCounts = new TreeMap<String, Integer>();
|
||||||
|
|
||||||
|
public <L> L createListener(Class<L> listenerType)
|
||||||
|
{
|
||||||
|
return listenerType.cast(Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
|
||||||
|
new Class[]{listenerType},
|
||||||
|
this));
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getEventCount(String eventName)
|
||||||
|
{
|
||||||
|
Integer count = eventCounts.get(eventName);
|
||||||
|
return count == null ? 0 : count;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
|
||||||
|
{
|
||||||
|
Integer count = eventCounts.get(method.getName());
|
||||||
|
if (count == null)
|
||||||
|
{
|
||||||
|
eventCounts.put(method.getName(), 1);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
eventCounts.put(method.getName(), count + 1);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class PropertyChangeSource
|
||||||
|
{
|
||||||
|
private EventListenerSupport<PropertyChangeListener> listeners = EventListenerSupport.create(PropertyChangeListener.class);
|
||||||
|
|
||||||
|
private String property;
|
||||||
|
|
||||||
|
public void setProperty(String property)
|
||||||
|
{
|
||||||
|
String oldValue = this.property;
|
||||||
|
this.property = property;
|
||||||
|
listeners.fire().propertyChange(new PropertyChangeEvent(this, "property", "oldValue", property));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addPropertyChangeListener(PropertyChangeListener listener)
|
||||||
|
{
|
||||||
|
listeners.addListener(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removePropertyChangeListener(PropertyChangeListener listener)
|
||||||
|
{
|
||||||
|
listeners.removeListener(listener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue