class MethodInvokerUtils {

	private 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
	 */
	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.
	 */
	@Nullable
	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.
	 */
	@Nullable
	public static MethodInvoker getMethodInvokerByAnnotation(final Class<? extends Annotation> annotationType,
			final Object target) {<FILL_FUNCTION_BODY>}

	/**
	 * Create a {@link MethodInvoker} for the delegate from a single public method.
	 * @param target an object to search for an appropriate method.
	 * @return a {@link MethodInvoker} that calls a method on the delegate.
	 */
	public static 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);
	}

}

		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());
		}
	
<no_super_class>