Author: jcarman
Date: Thu Jul 22 11:35:07 2010
New Revision: 966589

URL: http://svn.apache.org/viewvc?rev=966589&view=rev
Log:
Misc. event utils.

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

Added: 
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=966589&view=auto
==============================================================================
--- 
commons/proper/lang/trunk/src/main/java/org/apache/commons/lang3/event/EventListenerSupport.java
 (added)
+++ 
commons/proper/lang/trunk/src/main/java/org/apache/commons/lang3/event/EventListenerSupport.java
 Thu Jul 22 11:35:07 2010
@@ -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;
+        }
+    }
+}

Added: 
commons/proper/lang/trunk/src/main/java/org/apache/commons/lang3/event/EventUtils.java
URL: 
http://svn.apache.org/viewvc/commons/proper/lang/trunk/src/main/java/org/apache/commons/lang3/event/EventUtils.java?rev=966589&view=auto
==============================================================================
--- 
commons/proper/lang/trunk/src/main/java/org/apache/commons/lang3/event/EventUtils.java
 (added)
+++ 
commons/proper/lang/trunk/src/main/java/org/apache/commons/lang3/event/EventUtils.java
 Thu Jul 22 11:35:07 2010
@@ -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;
+        }
+    }
+}

Added: 
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=966589&view=auto
==============================================================================
--- 
commons/proper/lang/trunk/src/test/java/org/apache/commons/lang3/event/EventListenerSupportTest.java
 (added)
+++ 
commons/proper/lang/trunk/src/test/java/org/apache/commons/lang3/event/EventListenerSupportTest.java
 Thu Jul 22 11:35:07 2010
@@ -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);
+            }
+        };
+    }
+}

Added: 
commons/proper/lang/trunk/src/test/java/org/apache/commons/lang3/event/EventUtilsTest.java
URL: 
http://svn.apache.org/viewvc/commons/proper/lang/trunk/src/test/java/org/apache/commons/lang3/event/EventUtilsTest.java?rev=966589&view=auto
==============================================================================
--- 
commons/proper/lang/trunk/src/test/java/org/apache/commons/lang3/event/EventUtilsTest.java
 (added)
+++ 
commons/proper/lang/trunk/src/test/java/org/apache/commons/lang3/event/EventUtilsTest.java
 Thu Jul 22 11:35:07 2010
@@ -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);
+        }
+    }
+}


Reply via email to