/* * Copyright 2002-2008 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.batch.support; import java.lang.annotation.Annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Target; import java.lang.reflect.Method; import java.util.concurrent.atomic.AtomicReference; import org.springframework.aop.framework.Advised; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.ObjectUtils; import org.springframework.util.ReflectionUtils; /** * Utility methods for create MethodInvoker instances. * * @author Lucas Ward * @since 2.0 */ public class MethodInvokerUtils { /** * Create a {@link MethodInvoker} using the provided method name to search. * * @param object to be invoked * @param methodName of the method to be invoked * @param paramsRequired boolean indicating whether the parameters are * required, if false, a no args version of the method will be searched for. * @param paramTypes - parameter types of the method to search for. * @return MethodInvoker if the method is found, null if it is not. */ public static MethodInvoker getMethodInvokerByName(Object object, String methodName, boolean paramsRequired, Class<?>... paramTypes) { Assert.notNull(object, "Object to invoke must not be null"); Method method = ClassUtils.getMethodIfAvailable(object.getClass(), methodName, paramTypes); if (method == null) { String errorMsg = "no method found with name [" + methodName + "] on class [" + object.getClass().getSimpleName() + "] compatible with the signature [" + getParamTypesString(paramTypes) + "]."; Assert.isTrue(!paramsRequired, errorMsg); // if no method was found for the given parameters, and the // parameters aren't required, then try with no params method = ClassUtils.getMethodIfAvailable(object.getClass(), methodName); Assert.notNull(method, errorMsg); } return new SimpleMethodInvoker(object, method); } /** * Create a String representation of the array of parameter types. * * @param paramTypes types of the parameters to be used * @return String a String representation of those types */ public static String getParamTypesString(Class<?>... paramTypes) { StringBuilder paramTypesList = new StringBuilder("("); for (int i = 0; i < paramTypes.length; i++) { paramTypesList.append(paramTypes[i].getSimpleName()); if (i + 1 < paramTypes.length) { paramTypesList.append(", "); } } return paramTypesList.append(")").toString(); } /** * Create a {@link MethodInvoker} using the provided interface, and method * name from that interface. * * @param cls the interface to search for the method named * @param methodName of the method to be invoked * @param object to be invoked * @param paramTypes - parameter types of the method to search for. * @return MethodInvoker if the method is found, null if it is not. */ public static MethodInvoker getMethodInvokerForInterface(Class<?> cls, String methodName, Object object, Class<?>... paramTypes) { if (cls.isAssignableFrom(object.getClass())) { return MethodInvokerUtils.getMethodInvokerByName(object, methodName, true, paramTypes); } else { return null; } } /** * Create a MethodInvoker from the delegate based on the annotationType. * Ensure that the annotated method has a valid set of parameters. * * @param annotationType the annotation to scan for * @param target the target object * @param expectedParamTypes the expected parameter types for the method * @return a MethodInvoker */ public static MethodInvoker getMethodInvokerByAnnotation(final Class<? extends Annotation> annotationType, final Object target, final Class<?>... expectedParamTypes) { MethodInvoker mi = MethodInvokerUtils.getMethodInvokerByAnnotation(annotationType, target); final Class<?> targetClass = (target instanceof Advised) ? ((Advised) target).getTargetSource() .getTargetClass() : target.getClass(); if (mi != null) { ReflectionUtils.doWithMethods(targetClass, method -> { Annotation annotation = AnnotationUtils.findAnnotation(method, annotationType); if (annotation != null) { Class<?>[] paramTypes = method.getParameterTypes(); if (paramTypes.length > 0) { String errorMsg = "The method [" + method.getName() + "] on target class [" + targetClass.getSimpleName() + "] is incompatible with the signature [" + getParamTypesString(expectedParamTypes) + "] expected for the annotation [" + annotationType.getSimpleName() + "]."; Assert.isTrue(paramTypes.length == expectedParamTypes.length, errorMsg); for (int i = 0; i < paramTypes.length; i++) { Assert.isTrue(expectedParamTypes[i].isAssignableFrom(paramTypes[i]), errorMsg); } } } }); } return mi; } /** * Create {@link MethodInvoker} for the method with the provided annotation * on the provided object. Annotations that cannot be applied to methods * (i.e. that aren't annotated with an element type of METHOD) will cause an * exception to be thrown. * * @param annotationType to be searched for * @param target to be invoked * @return MethodInvoker for the provided annotation, null if none is found. */ public static MethodInvoker getMethodInvokerByAnnotation(final Class<? extends Annotation> annotationType, final Object target) { Assert.notNull(target, "Target must not be null"); Assert.notNull(annotationType, "AnnotationType must not be null"); Assert.isTrue(ObjectUtils.containsElement(annotationType.getAnnotation(Target.class).value(), ElementType.METHOD), "Annotation [" + annotationType + "] is not a Method-level annotation."); final Class<?> targetClass = (target instanceof Advised) ? ((Advised) target).getTargetSource() .getTargetClass() : target.getClass(); if (targetClass == null) { // Proxy with no target cannot have annotations return null; } final AtomicReference<Method> annotatedMethod = new AtomicReference<>(); ReflectionUtils.doWithMethods(targetClass, method -> { Annotation annotation = AnnotationUtils.findAnnotation(method, annotationType); if (annotation != null) { Assert.isNull(annotatedMethod.get(), "found more than one method on target class [" + targetClass.getSimpleName() + "] with the annotation type [" + annotationType.getSimpleName() + "]."); annotatedMethod.set(method); } }); Method method = annotatedMethod.get(); if (method == null) { return null; } else { return new SimpleMethodInvoker(target, annotatedMethod.get()); } } /** * Create a {@link MethodInvoker} for the delegate from a single public * method. * * @param target an object to search for an appropriate method * @return a MethodInvoker that calls a method on the delegate */ public static <C, T> MethodInvoker getMethodInvokerForSingleArgument(Object target) { final AtomicReference<Method> methodHolder = new AtomicReference<>(); ReflectionUtils.doWithMethods(target.getClass(), method -> { if (method.getParameterTypes() == null || method.getParameterTypes().length != 1) { return; } if (method.getReturnType().equals(Void.TYPE) || ReflectionUtils.isEqualsMethod(method)) { return; } Assert.state(methodHolder.get() == null, "More than one non-void public method detected with single argument."); methodHolder.set(method); }); Method method = methodHolder.get(); return new SimpleMethodInvoker(target, method); } }