001/* 002 * Copyright 2002-2008 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.springframework.batch.support; 017 018import java.lang.annotation.Annotation; 019import java.lang.annotation.ElementType; 020import java.lang.annotation.Target; 021import java.lang.reflect.Method; 022import java.util.concurrent.atomic.AtomicReference; 023 024import org.springframework.aop.framework.Advised; 025import org.springframework.core.annotation.AnnotationUtils; 026import org.springframework.util.Assert; 027import org.springframework.util.ClassUtils; 028import org.springframework.util.ObjectUtils; 029import org.springframework.util.ReflectionUtils; 030 031/** 032 * Utility methods for create MethodInvoker instances. 033 * 034 * @author Lucas Ward 035 * @since 2.0 036 */ 037public class MethodInvokerUtils { 038 039 /** 040 * Create a {@link MethodInvoker} using the provided method name to search. 041 * 042 * @param object to be invoked 043 * @param methodName of the method to be invoked 044 * @param paramsRequired boolean indicating whether the parameters are 045 * required, if false, a no args version of the method will be searched for. 046 * @param paramTypes - parameter types of the method to search for. 047 * @return MethodInvoker if the method is found, null if it is not. 048 */ 049 public static MethodInvoker getMethodInvokerByName(Object object, String methodName, boolean paramsRequired, 050 Class<?>... paramTypes) { 051 Assert.notNull(object, "Object to invoke must not be null"); 052 Method method = ClassUtils.getMethodIfAvailable(object.getClass(), methodName, paramTypes); 053 if (method == null) { 054 String errorMsg = "no method found with name [" + methodName + "] on class [" 055 + object.getClass().getSimpleName() + "] compatible with the signature [" 056 + getParamTypesString(paramTypes) + "]."; 057 Assert.isTrue(!paramsRequired, errorMsg); 058 // if no method was found for the given parameters, and the 059 // parameters aren't required, then try with no params 060 method = ClassUtils.getMethodIfAvailable(object.getClass(), methodName); 061 Assert.notNull(method, errorMsg); 062 } 063 return new SimpleMethodInvoker(object, method); 064 } 065 066 /** 067 * Create a String representation of the array of parameter types. 068 * 069 * @param paramTypes types of the parameters to be used 070 * @return String a String representation of those types 071 */ 072 public static String getParamTypesString(Class<?>... paramTypes) { 073 StringBuilder paramTypesList = new StringBuilder("("); 074 for (int i = 0; i < paramTypes.length; i++) { 075 paramTypesList.append(paramTypes[i].getSimpleName()); 076 if (i + 1 < paramTypes.length) { 077 paramTypesList.append(", "); 078 } 079 } 080 return paramTypesList.append(")").toString(); 081 } 082 083 /** 084 * Create a {@link MethodInvoker} using the provided interface, and method 085 * name from that interface. 086 * 087 * @param cls the interface to search for the method named 088 * @param methodName of the method to be invoked 089 * @param object to be invoked 090 * @param paramTypes - parameter types of the method to search for. 091 * @return MethodInvoker if the method is found, null if it is not. 092 */ 093 public static MethodInvoker getMethodInvokerForInterface(Class<?> cls, String methodName, Object object, 094 Class<?>... paramTypes) { 095 096 if (cls.isAssignableFrom(object.getClass())) { 097 return MethodInvokerUtils.getMethodInvokerByName(object, methodName, true, paramTypes); 098 } 099 else { 100 return null; 101 } 102 } 103 104 /** 105 * Create a MethodInvoker from the delegate based on the annotationType. 106 * Ensure that the annotated method has a valid set of parameters. 107 * 108 * @param annotationType the annotation to scan for 109 * @param target the target object 110 * @param expectedParamTypes the expected parameter types for the method 111 * @return a MethodInvoker 112 */ 113 public static MethodInvoker getMethodInvokerByAnnotation(final Class<? extends Annotation> annotationType, 114 final Object target, final Class<?>... expectedParamTypes) { 115 MethodInvoker mi = MethodInvokerUtils.getMethodInvokerByAnnotation(annotationType, target); 116 final Class<?> targetClass = (target instanceof Advised) ? ((Advised) target).getTargetSource() 117 .getTargetClass() : target.getClass(); 118 if (mi != null) { 119 ReflectionUtils.doWithMethods(targetClass, method -> { 120 Annotation annotation = AnnotationUtils.findAnnotation(method, annotationType); 121 if (annotation != null) { 122 Class<?>[] paramTypes = method.getParameterTypes(); 123 if (paramTypes.length > 0) { 124 String errorMsg = "The method [" + method.getName() + "] on target class [" 125 + targetClass.getSimpleName() + "] is incompatible with the signature [" 126 + getParamTypesString(expectedParamTypes) + "] expected for the annotation [" 127 + annotationType.getSimpleName() + "]."; 128 129 Assert.isTrue(paramTypes.length == expectedParamTypes.length, errorMsg); 130 for (int i = 0; i < paramTypes.length; i++) { 131 Assert.isTrue(expectedParamTypes[i].isAssignableFrom(paramTypes[i]), errorMsg); 132 } 133 } 134 } 135 }); 136 } 137 return mi; 138 } 139 140 /** 141 * Create {@link MethodInvoker} for the method with the provided annotation 142 * on the provided object. Annotations that cannot be applied to methods 143 * (i.e. that aren't annotated with an element type of METHOD) will cause an 144 * exception to be thrown. 145 * 146 * @param annotationType to be searched for 147 * @param target to be invoked 148 * @return MethodInvoker for the provided annotation, null if none is found. 149 */ 150 public static MethodInvoker getMethodInvokerByAnnotation(final Class<? extends Annotation> annotationType, 151 final Object target) { 152 Assert.notNull(target, "Target must not be null"); 153 Assert.notNull(annotationType, "AnnotationType must not be null"); 154 Assert.isTrue(ObjectUtils.containsElement(annotationType.getAnnotation(Target.class).value(), 155 ElementType.METHOD), "Annotation [" + annotationType + "] is not a Method-level annotation."); 156 final Class<?> targetClass = (target instanceof Advised) ? ((Advised) target).getTargetSource() 157 .getTargetClass() : target.getClass(); 158 if (targetClass == null) { 159 // Proxy with no target cannot have annotations 160 return null; 161 } 162 final AtomicReference<Method> annotatedMethod = new AtomicReference<>(); 163 ReflectionUtils.doWithMethods(targetClass, method -> { 164 Annotation annotation = AnnotationUtils.findAnnotation(method, annotationType); 165 if (annotation != null) { 166 Assert.isNull(annotatedMethod.get(), "found more than one method on target class [" 167 + targetClass.getSimpleName() + "] with the annotation type [" 168 + annotationType.getSimpleName() + "]."); 169 annotatedMethod.set(method); 170 } 171 }); 172 Method method = annotatedMethod.get(); 173 if (method == null) { 174 return null; 175 } 176 else { 177 return new SimpleMethodInvoker(target, annotatedMethod.get()); 178 } 179 } 180 181 /** 182 * Create a {@link MethodInvoker} for the delegate from a single public 183 * method. 184 * 185 * @param target an object to search for an appropriate method. 186 * @param <C> the class. 187 * @param <T> the type. 188 * @return a {@link MethodInvoker} that calls a method on the delegate. 189 */ 190 public static <C, T> MethodInvoker getMethodInvokerForSingleArgument(Object target) { 191 final AtomicReference<Method> methodHolder = new AtomicReference<>(); 192 ReflectionUtils.doWithMethods(target.getClass(), method -> { 193 if (method.getParameterTypes() == null || method.getParameterTypes().length != 1) { 194 return; 195 } 196 if (method.getReturnType().equals(Void.TYPE) || ReflectionUtils.isEqualsMethod(method)) { 197 return; 198 } 199 Assert.state(methodHolder.get() == null, 200 "More than one non-void public method detected with single argument."); 201 methodHolder.set(method); 202 }); 203 Method method = methodHolder.get(); 204 return new SimpleMethodInvoker(target, method); 205 } 206}