    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 *      https://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 SecurityException if an underlying accessible object's method denies the request.
    072         * @see SecurityManager#checkPermission
    073         * @throws Throwable if an error occurs
    074         */
    075        @Override
    076        public Object invoke(final Object proxy, final Method method, final Object[] parameters) throws Throwable {
    077            if (eventTypes.isEmpty() || eventTypes.contains(method.getName())) {
    078                if (hasMatchingParametersMethod(method)) {
    079                    return MethodUtils.invokeMethod(target, methodName, parameters);
    080                }
    081                return MethodUtils.invokeMethod(target, methodName);
    082            }
    083            return null;
    084        }
    085    }
    086
    087    /**
    088     * Adds an event listener to the specified source.  This looks for an "add" method corresponding to the event
    089     * type (addActionListener, for example).
    090     * @param eventSource   the event source
    091     * @param listenerType  the event listener type
    092     * @param listener      the listener
    093     * @param <L>           the event listener type
    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 ReflectiveOperationException e) {
    100            throw new IllegalArgumentException("Unable to add listener for class " + eventSource.getClass().getName()
    101                    + " and public add" + listenerType.getSimpleName()
    102                    + " method which takes a parameter of type " + listenerType.getName() + ".");
    103        }
    104    }
    105
    106    /**
    107     * Binds an event listener to a specific method on a specific object.
    108     *
    109     * @param <L>          the event listener type
    110     * @param target       the target object
    111     * @param methodName   the name of the method to be called
    112     * @param eventSource  the object which is generating events (JButton, JList, etc.)
    113     * @param listenerType the listener interface (ActionListener.class, SelectionListener.class, etc.)
    114     * @param eventTypes   the event types (method names) from the listener interface (if none specified, all will be
    115     *                     supported)
    116     */
    117    public static <L> void bindEventsToMethod(final Object target, final String methodName, final Object eventSource,
    118            final Class<L> listenerType, final String... eventTypes) {
    119        final L listener = listenerType.cast(Proxy.newProxyInstance(target.getClass().getClassLoader(),
    120                new Class[] { listenerType }, new EventBindingInvocationHandler(target, methodName, eventTypes)));
    121        addEventListener(eventSource, listenerType, listener);
    122    }
    123
    124    /**
    125     * Make private in 4.0.
    126     *
    127     * @deprecated TODO Make private in 4.0.
    128     */
    129    @Deprecated
    130    public EventUtils() {
    131        // empty
    132    }
    133}