    001/*
    002 * Licensed to the Apache Software Foundation (ASF) under one or more
    003 * contributor license agreements.  See the NOTICE file distributed with
    004 * this work for additional information regarding copyright ownership.
    005 * The ASF licenses this file to You under the Apache License, Version 2.0
    006 * (the "License"); you may not use this file except in compliance with
    007 * the License.  You may obtain a copy of the License at
    008 *
    009 *      http://www.apache.org/licenses/LICENSE-2.0
    010 *
    011 * Unless required by applicable law or agreed to in writing, software
    012 * distributed under the License is distributed on an "AS IS" BASIS,
    013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014 * See the License for the specific language governing permissions and
    015 * limitations under the License.
    016 */
    017
    018package org.apache.commons.lang3.event;
    019
    020import java.lang.reflect.InvocationHandler;
    021import java.lang.reflect.InvocationTargetException;
    022import java.lang.reflect.Method;
    023import java.lang.reflect.Proxy;
    024import java.util.Arrays;
    025import java.util.HashSet;
    026import java.util.Set;
    027
    028import org.apache.commons.lang3.reflect.MethodUtils;
    029
    030/**
    031 * Provides some useful event-based utility methods.
    032 *
    033 * @since 3.0
    034 */
    035public class EventUtils {
    036
    037    /**
    038     * Adds an event listener to the specified source.  This looks for an "add" method corresponding to the event
    039     * type (addActionListener, for example).
    040     * @param eventSource   the event source
    041     * @param listenerType  the event listener type
    042     * @param listener      the listener
    043     * @param <L>           the event listener type
    044     *
    045     * @throws IllegalArgumentException if the object doesn't support the listener type
    046     */
    047    public static <L> void addEventListener(final Object eventSource, final Class<L> listenerType, final L listener) {
    048        try {
    049            MethodUtils.invokeMethod(eventSource, "add" + listenerType.getSimpleName(), listener);
    050        } catch (final NoSuchMethodException e) {
    051            throw new IllegalArgumentException("Class " + eventSource.getClass().getName()
    052                    + " does not have a public add" + listenerType.getSimpleName()
    053                    + " method which takes a parameter of type " + listenerType.getName() + ".");
    054        } catch (final IllegalAccessException e) {
    055            throw new IllegalArgumentException("Class " + eventSource.getClass().getName()
    056                    + " does not have an accessible add" + listenerType.getSimpleName ()
    057                    + " method which takes a parameter of type " + listenerType.getName() + ".");
    058        } catch (final InvocationTargetException e) {
    059            throw new RuntimeException("Unable to add listener.", e.getCause());
    060        }
    061    }
    062
    063    /**
    064     * Binds an event listener to a specific method on a specific object.
    065     *
    066     * @param <L>          the event listener type
    067     * @param target       the target object
    068     * @param methodName   the name of the method to be called
    069     * @param eventSource  the object which is generating events (JButton, JList, etc.)
    070     * @param listenerType the listener interface (ActionListener.class, SelectionListener.class, etc.)
    071     * @param eventTypes   the event types (method names) from the listener interface (if none specified, all will be
    072     *                     supported)
    073     */
    074    public static <L> void bindEventsToMethod(final Object target, final String methodName, final Object eventSource,
    075            final Class<L> listenerType, final String... eventTypes) {
    076        final L listener = listenerType.cast(Proxy.newProxyInstance(target.getClass().getClassLoader(),
    077                new Class[] { listenerType }, new EventBindingInvocationHandler(target, methodName, eventTypes)));
    078        addEventListener(eventSource, listenerType, listener);
    079    }
    080
    081    private static class EventBindingInvocationHandler implements InvocationHandler {
    082        private final Object target;
    083        private final String methodName;
    084        private final Set<String> eventTypes;
    085
    086        /**
    087         * Creates a new instance of {@code EventBindingInvocationHandler}.
    088         *
    089         * @param target the target object for method invocations
    090         * @param methodName the name of the method to be invoked
    091         * @param eventTypes the names of the supported event types
    092         */
    093        EventBindingInvocationHandler(final Object target, final String methodName, final String[] eventTypes) {
    094            this.target = target;
    095            this.methodName = methodName;
    096            this.eventTypes = new HashSet<>(Arrays.asList(eventTypes));
    097        }
    098
    099        /**
    100         * Handles a method invocation on the proxy object.
    101         *
    102         * @param proxy the proxy instance
    103         * @param method the method to be invoked
    104         * @param parameters the parameters for the method invocation
    105         * @return the result of the method call
    106         * @throws Throwable if an error occurs
    107         */
    108        @Override
    109        public Object invoke(final Object proxy, final Method method, final Object[] parameters) throws Throwable {
    110            if (eventTypes.isEmpty() || eventTypes.contains(method.getName())) {
    111                if (hasMatchingParametersMethod(method)) {
    112                    return MethodUtils.invokeMethod(target, methodName, parameters);
    113                }
    114                return MethodUtils.invokeMethod(target, methodName);
    115            }
    116            return null;
    117        }
    118
    119        /**
    120         * Checks whether a method for the passed in parameters can be found.
    121         *
    122         * @param method the listener method invoked
    123         * @return a flag whether the parameters could be matched
    124         */
    125        private boolean hasMatchingParametersMethod(final Method method) {
    126            return MethodUtils.getAccessibleMethod(target.getClass(), methodName, method.getParameterTypes()) != null;
    127        }
    128    }
    129}