View Javadoc

1   /*
2    * Copyright 2002-2008 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.springframework.batch.support;
17  
18  import java.lang.annotation.Annotation;
19  import java.lang.annotation.ElementType;
20  import java.lang.annotation.Target;
21  import java.lang.reflect.Method;
22  import java.util.concurrent.atomic.AtomicReference;
23  
24  import org.springframework.aop.framework.Advised;
25  import org.springframework.core.annotation.AnnotationUtils;
26  import org.springframework.util.Assert;
27  import org.springframework.util.ClassUtils;
28  import org.springframework.util.ObjectUtils;
29  import org.springframework.util.ReflectionUtils;
30  
31  /**
32   * Utility methods for create MethodInvoker instances.
33   * 
34   * @author Lucas Ward
35   * @since 2.0
36   */
37  public class MethodInvokerUtils {
38  
39  	/**
40  	 * Create a {@link MethodInvoker} using the provided method name to search.
41  	 * 
42  	 * @param object to be invoked
43  	 * @param methodName of the method to be invoked
44  	 * @param paramsRequired boolean indicating whether the parameters are
45  	 * required, if false, a no args version of the method will be searched for.
46  	 * @param paramTypes - parameter types of the method to search for.
47  	 * @return MethodInvoker if the method is found, null if it is not.
48  	 */
49  	public static MethodInvoker getMethodInvokerByName(Object object, String methodName, boolean paramsRequired,
50  			Class<?>... paramTypes) {
51  		Assert.notNull(object, "Object to invoke must not be null");
52  		Method method = ClassUtils.getMethodIfAvailable(object.getClass(), methodName, paramTypes);
53  		if (method == null) {
54  			String errorMsg = "no method found with name [" + methodName + "] on class ["
55  					+ object.getClass().getSimpleName() + "] compatable with the signature ["
56  					+ getParamTypesString(paramTypes) + "].";
57  			Assert.isTrue(!paramsRequired, errorMsg);
58  			// if no method was found for the given parameters, and the
59  			// parameters aren't required, then try with no params
60  			method = ClassUtils.getMethodIfAvailable(object.getClass(), methodName, new Class[] {});
61  			Assert.notNull(method, errorMsg);
62  		}
63  		return new SimpleMethodInvoker(object, method);
64  	}
65  
66  	/**
67  	 * Create a String representation of the array of parameter types.
68  	 * 
69  	 * @param paramTypes
70  	 * @return String
71  	 */
72  	public static String getParamTypesString(Class<?>... paramTypes) {
73  		StringBuffer paramTypesList = new StringBuffer("(");
74  		for (int i = 0; i < paramTypes.length; i++) {
75  			paramTypesList.append(paramTypes[i].getSimpleName());
76  			if (i + 1 < paramTypes.length) {
77  				paramTypesList.append(", ");
78  			}
79  		}
80  		return paramTypesList.append(")").toString();
81  	}
82  
83  	/**
84  	 * Create a {@link MethodInvoker} using the provided interface, and method
85  	 * name from that interface.
86  	 * 
87  	 * @param cls the interface to search for the method named
88  	 * @param methodName of the method to be invoked
89  	 * @param object to be invoked
90  	 * @param paramTypes - parameter types of the method to search for.
91  	 * @return MethodInvoker if the method is found, null if it is not.
92  	 */
93  	public static MethodInvoker getMethodInvokerForInterface(Class<?> cls, String methodName, Object object,
94  			Class<?>... paramTypes) {
95  
96  		if (cls.isAssignableFrom(object.getClass())) {
97  			return MethodInvokerUtils.getMethodInvokerByName(object, methodName, true, paramTypes);
98  		}
99  		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, new ReflectionUtils.MethodCallback() {
120                 @Override
121 				public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
122 					Annotation annotation = AnnotationUtils.findAnnotation(method, annotationType);
123 					if (annotation != null) {
124 						Class<?>[] paramTypes = method.getParameterTypes();
125 						if (paramTypes.length > 0) {
126 							String errorMsg = "The method [" + method.getName() + "] on target class ["
127 									+ targetClass.getSimpleName() + "] is incompatable with the signature ["
128 									+ getParamTypesString(expectedParamTypes) + "] expected for the annotation ["
129 									+ annotationType.getSimpleName() + "].";
130 
131 							Assert.isTrue(paramTypes.length == expectedParamTypes.length, errorMsg);
132 							for (int i = 0; i < paramTypes.length; i++) {
133 								Assert.isTrue(expectedParamTypes[i].isAssignableFrom(paramTypes[i]), errorMsg);
134 							}
135 						}
136 					}
137 				}
138 			});
139 		}
140 		return mi;
141 	}
142 
143 	/**
144 	 * Create {@link MethodInvoker} for the method with the provided annotation
145 	 * on the provided object. Annotations that cannot be applied to methods
146 	 * (i.e. that aren't annotated with an element type of METHOD) will cause an
147 	 * exception to be thrown.
148 	 * 
149 	 * @param annotationType to be searched for
150 	 * @param target to be invoked
151 	 * @return MethodInvoker for the provided annotation, null if none is found.
152 	 */
153 	public static MethodInvoker getMethodInvokerByAnnotation(final Class<? extends Annotation> annotationType,
154 			final Object target) {
155 		Assert.notNull(target, "Target must not be null");
156 		Assert.notNull(annotationType, "AnnotationType must not be null");
157 		Assert.isTrue(ObjectUtils.containsElement(annotationType.getAnnotation(Target.class).value(),
158 				ElementType.METHOD), "Annotation [" + annotationType + "] is not a Method-level annotation.");
159 		final Class<?> targetClass = (target instanceof Advised) ? ((Advised) target).getTargetSource()
160 				.getTargetClass() : target.getClass();
161 		if (targetClass == null) {
162 			// Proxy with no target cannot have annotations
163 			return null;
164 		}
165 		final AtomicReference<Method> annotatedMethod = new AtomicReference<Method>();
166 		ReflectionUtils.doWithMethods(targetClass, new ReflectionUtils.MethodCallback() {
167             @Override
168 			public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
169 				Annotation annotation = AnnotationUtils.findAnnotation(method, annotationType);
170 				if (annotation != null) {
171 					Assert.isNull(annotatedMethod.get(), "found more than one method on target class ["
172 							+ targetClass.getSimpleName() + "] with the annotation type ["
173 							+ annotationType.getSimpleName() + "].");
174 					annotatedMethod.set(method);
175 				}
176 			}
177 		});
178 		Method method = annotatedMethod.get();
179 		if (method == null) {
180 			return null;
181 		}
182 		else {
183 			return new SimpleMethodInvoker(target, annotatedMethod.get());
184 		}
185 	}
186 
187 	/**
188 	 * Create a {@link MethodInvoker} for the delegate from a single public
189 	 * method.
190 	 * 
191 	 * @param target an object to search for an appropriate method
192 	 * @return a MethodInvoker that calls a method on the delegate
193 	 */
194 	public static <C, T> MethodInvoker getMethodInvokerForSingleArgument(Object target) {
195 		final AtomicReference<Method> methodHolder = new AtomicReference<Method>();
196 		ReflectionUtils.doWithMethods(target.getClass(), new ReflectionUtils.MethodCallback() {
197             @Override
198 			public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
199 				if (method.getParameterTypes() == null || method.getParameterTypes().length != 1) {
200 					return;
201 				}
202 				if (method.getReturnType().equals(Void.TYPE) || ReflectionUtils.isEqualsMethod(method)) {
203 					return;
204 				}
205 				Assert.state(methodHolder.get() == null,
206 						"More than one non-void public method detected with single argument.");
207 				methodHolder.set(method);
208 			}
209 		});
210 		Method method = methodHolder.get();
211 		return new SimpleMethodInvoker(target, method);
212 	}
213 }