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.ObjectUtils; 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 * Tests 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 SecurityException Thrown if an underlying accessible object's method denies the request. 073 * @see SecurityManager#checkPermission 074 * @throws Throwable Thrown if an error occurs. 075 */ 076 @Override 077 public Object invoke(final Object proxy, final Method method, final Object[] parameters) throws Throwable { 078 if (method.getDeclaringClass() == Object.class) { 079 // Handle Object methods locally instead of dispatching them to the bound target, 080 // mirroring java.beans.EventHandler: routine host actions (hash-based collections, 081 // logging, equality checks during listener de-registration) must not invoke the 082 // target method and must not return null into an unboxing context. 083 switch (method.getName()) { 084 case "hashCode": 085 return Integer.valueOf(System.identityHashCode(proxy)); 086 case "equals": 087 return Boolean.valueOf(proxy == parameters[0]); 088 default: // toString 089 return ObjectUtils.identityToString(proxy); 090 } 091 } 092 if (eventTypes.isEmpty() || eventTypes.contains(method.getName())) { 093 if (hasMatchingParametersMethod(method)) { 094 return MethodUtils.invokeMethod(target, methodName, parameters); 095 } 096 return MethodUtils.invokeMethod(target, methodName); 097 } 098 return null; 099 } 100 } 101 102 /** 103 * Adds an event listener to the specified source. This looks for an "add" method corresponding to the event 104 * type (addActionListener, for example). 105 * 106 * @param eventSource The event source. 107 * @param listenerType The event listener type. 108 * @param listener The listener. 109 * @param <L> the event listener type. 110 * @throws IllegalArgumentException Thrown if the object doesn't support the listener type. 111 */ 112 public static <L> void addEventListener(final Object eventSource, final Class<L> listenerType, final L listener) { 113 try { 114 MethodUtils.invokeMethod(eventSource, "add" + listenerType.getSimpleName(), listener); 115 } catch (final ReflectiveOperationException e) { 116 throw new IllegalArgumentException("Unable to add listener for class " + eventSource.getClass().getName() 117 + " and public add" + listenerType.getSimpleName() 118 + " method which takes a parameter of type " + listenerType.getName() + "."); 119 } 120 } 121 122 /** 123 * Binds an event listener to a specific method on a specific object. 124 * 125 * @param <L> the event listener type. 126 * @param target The target object. 127 * @param methodName The name of the method to be called. 128 * @param eventSource The object which is generating events (JButton, JList, etc.). 129 * @param listenerType The listener interface (ActionListener.class, SelectionListener.class, etc.). 130 * @param eventTypes The event types (method names) from the listener interface (if none specified, all will be 131 * supported). 132 */ 133 public static <L> void bindEventsToMethod(final Object target, final String methodName, final Object eventSource, 134 final Class<L> listenerType, final String... eventTypes) { 135 final L listener = listenerType.cast(Proxy.newProxyInstance(target.getClass().getClassLoader(), 136 new Class[] { listenerType }, new EventBindingInvocationHandler(target, methodName, eventTypes))); 137 addEventListener(eventSource, listenerType, listener); 138 } 139 140 /** 141 * Make private in 4.0. 142 * 143 * @deprecated TODO Make private in 4.0. 144 */ 145 @Deprecated 146 public EventUtils() { 147 // empty 148 } 149}