    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    private static final class EventBindingInvocationHandler implements InvocationHandler {
    038        private final Object target;
    039        private final String methodName;
    040        private final Set<String> eventTypes;
    041
    042        /**
    043         * Creates a new instance of {@link EventBindingInvocationHandler}.
    044         *
    045         * @param target the target object for method invocations
    046         * @param methodName the name of the method to be invoked
    047         * @param eventTypes the names of the supported event types
    048         */
    049        EventBindingInvocationHandler(final Object target, final String methodName, final String[] eventTypes) {
    050            this.target = target;
    051            this.methodName = methodName;
    052            this.eventTypes = new HashSet<>(Arrays.asList(eventTypes));
    053        }
    054
    055        /**
    056         * Checks whether a method for the passed in parameters can be found.
    057         *
    058         * @param method the listener method invoked
    059         * @return a flag whether the parameters could be matched
    060         */
    061        private boolean hasMatchingParametersMethod(final Method method) {
    062            return MethodUtils.getAccessibleMethod(target.getClass(), methodName, method.getParameterTypes()) != null;
    063        }
    064
    065        /**
    066         * Handles a method invocation on the proxy object.
    067         *
    068         * @param proxy the proxy instance
    069         * @param method the method to be invoked
    070         * @param parameters the parameters for the method invocation
    071         * @return the result of the method call
    072         * @throws Throwable if an error occurs
    073         */
    074        @Override
    075        public Object invoke(final Object proxy, final Method method, final Object[] parameters) throws Throwable {
    076            if (eventTypes.isEmpty() || eventTypes.contains(method.getName())) {
    077                if (hasMatchingParametersMethod(method)) {
    078                    return MethodUtils.invokeMethod(target, methodName, parameters);
    079                }
    080                return MethodUtils.invokeMethod(target, methodName);
    081            }
    082            return null;
    083        }
    084    }
    085
    086    /**
    087     * Adds an event listener to the specified source.  This looks for an "add" method corresponding to the event
    088     * type (addActionListener, for example).
    089     * @param eventSource   the event source
    090     * @param listenerType  the event listener type
    091     * @param listener      the listener
    092     * @param <L>           the event listener type
    093     *
    094     * @throws IllegalArgumentException if the object doesn't support the listener type
    095     */
    096    public static <L> void addEventListener(final Object eventSource, final Class<L> listenerType, final L listener) {
    097        try {
    098            MethodUtils.invokeMethod(eventSource, "add" + listenerType.getSimpleName(), listener);
    099        } catch (final NoSuchMethodException e) {
    100            throw new IllegalArgumentException("Class " + eventSource.getClass().getName()
    101                    + " does not have a public add" + listenerType.getSimpleName()
    102                    + " method which takes a parameter of type " + listenerType.getName() + ".");
    103        } catch (final IllegalAccessException e) {
    104            throw new IllegalArgumentException("Class " + eventSource.getClass().getName()
    105                    + " does not have an accessible add" + listenerType.getSimpleName ()
    106                    + " method which takes a parameter of type " + listenerType.getName() + ".");
    107        } catch (final InvocationTargetException e) {
    108            throw new IllegalArgumentException("Unable to add listener.", e.getCause());
    109        }
    110    }
    111
    112    /**
    113     * Binds an event listener to a specific method on a specific object.
    114     *
    115     * @param <L>          the event listener type
    116     * @param target       the target object
    117     * @param methodName   the name of the method to be called
    118     * @param eventSource  the object which is generating events (JButton, JList, etc.)
    119     * @param listenerType the listener interface (ActionListener.class, SelectionListener.class, etc.)
    120     * @param eventTypes   the event types (method names) from the listener interface (if none specified, all will be
    121     *                     supported)
    122     */
    123    public static <L> void bindEventsToMethod(final Object target, final String methodName, final Object eventSource,
    124            final Class<L> listenerType, final String... eventTypes) {
    125        final L listener = listenerType.cast(Proxy.newProxyInstance(target.getClass().getClassLoader(),
    126                new Class[] { listenerType }, new EventBindingInvocationHandler(target, methodName, eventTypes)));
    127        addEventListener(eventSource, listenerType, listener);
    128    }
    129}