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