001: /*
002: * Copyright 2002-2007 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: * http://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: */
016:
017: package org.springframework.util;
018:
019: import java.lang.reflect.InvocationTargetException;
020: import java.lang.reflect.Method;
021: import java.lang.reflect.Modifier;
022:
023: /**
024: * Helper class that allows for specifying a method to invoke in a declarative
025: * fashion, be it static or non-static.
026: *
027: * <p>Usage: Specify "targetClass"/"targetMethod" or "targetObject"/"targetMethod",
028: * optionally specify arguments, prepare the invoker. Afterwards, you may
029: * invoke the method any number of times, obtaining the invocation result.
030: *
031: * <p>Typically not used directly but via its subclasses
032: * {@link org.springframework.beans.factory.config.MethodInvokingFactoryBean} and
033: * {@link org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean}.
034: *
035: * @author Colin Sampaleanu
036: * @author Juergen Hoeller
037: * @since 19.02.2004
038: * @see #prepare
039: * @see #invoke
040: * @see org.springframework.beans.factory.config.MethodInvokingFactoryBean
041: * @see org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean
042: */
043: public class MethodInvoker {
044:
045: private Class targetClass;
046:
047: private Object targetObject;
048:
049: private String targetMethod;
050:
051: private String staticMethod;
052:
053: private Object[] arguments = new Object[0];
054:
055: /** The method we will call */
056: private Method methodObject;
057:
058: /**
059: * Set the target class on which to call the target method.
060: * Only necessary when the target method is static; else,
061: * a target object needs to be specified anyway.
062: * @see #setTargetObject
063: * @see #setTargetMethod
064: */
065: public void setTargetClass(Class targetClass) {
066: this .targetClass = targetClass;
067: }
068:
069: /**
070: * Return the target class on which to call the target method.
071: */
072: public Class getTargetClass() {
073: return this .targetClass;
074: }
075:
076: /**
077: * Set the target object on which to call the target method.
078: * Only necessary when the target method is not static;
079: * else, a target class is sufficient.
080: * @see #setTargetClass
081: * @see #setTargetMethod
082: */
083: public void setTargetObject(Object targetObject) {
084: this .targetObject = targetObject;
085: if (targetObject != null) {
086: this .targetClass = targetObject.getClass();
087: }
088: }
089:
090: /**
091: * Return the target object on which to call the target method.
092: */
093: public Object getTargetObject() {
094: return this .targetObject;
095: }
096:
097: /**
098: * Set the name of the method to be invoked.
099: * Refers to either a static method or a non-static method,
100: * depending on a target object being set.
101: * @see #setTargetClass
102: * @see #setTargetObject
103: */
104: public void setTargetMethod(String targetMethod) {
105: this .targetMethod = targetMethod;
106: }
107:
108: /**
109: * Return the name of the method to be invoked.
110: */
111: public String getTargetMethod() {
112: return this .targetMethod;
113: }
114:
115: /**
116: * Set a fully qualified static method name to invoke,
117: * e.g. "example.MyExampleClass.myExampleMethod".
118: * Convenient alternative to specifying targetClass and targetMethod.
119: * @see #setTargetClass
120: * @see #setTargetMethod
121: */
122: public void setStaticMethod(String staticMethod) {
123: this .staticMethod = staticMethod;
124: }
125:
126: /**
127: * Set arguments for the method invocation. If this property is not set,
128: * or the Object array is of length 0, a method with no arguments is assumed.
129: */
130: public void setArguments(Object[] arguments) {
131: this .arguments = (arguments != null ? arguments : new Object[0]);
132: }
133:
134: /**
135: * Retrun the arguments for the method invocation.
136: */
137: public Object[] getArguments() {
138: return this .arguments;
139: }
140:
141: /**
142: * Prepare the specified method.
143: * The method can be invoked any number of times afterwards.
144: * @see #getPreparedMethod
145: * @see #invoke
146: */
147: public void prepare() throws ClassNotFoundException,
148: NoSuchMethodException {
149: if (this .staticMethod != null) {
150: int lastDotIndex = this .staticMethod.lastIndexOf('.');
151: if (lastDotIndex == -1
152: || lastDotIndex == this .staticMethod.length()) {
153: throw new IllegalArgumentException(
154: "staticMethod must be a fully qualified class plus method name: "
155: + "e.g. 'example.MyExampleClass.myExampleMethod'");
156: }
157: String className = this .staticMethod.substring(0,
158: lastDotIndex);
159: String methodName = this .staticMethod
160: .substring(lastDotIndex + 1);
161: this .targetClass = resolveClassName(className);
162: this .targetMethod = methodName;
163: }
164:
165: Class targetClass = getTargetClass();
166: String targetMethod = getTargetMethod();
167: if (targetClass == null) {
168: throw new IllegalArgumentException(
169: "Either 'targetClass' or 'targetObject' is required");
170: }
171: if (targetMethod == null) {
172: throw new IllegalArgumentException(
173: "Property 'targetMethod' is required");
174: }
175:
176: Object[] arguments = getArguments();
177: Class[] argTypes = new Class[arguments.length];
178: for (int i = 0; i < arguments.length; ++i) {
179: argTypes[i] = (arguments[i] != null ? arguments[i]
180: .getClass() : Object.class);
181: }
182:
183: // Try to get the exact method first.
184: try {
185: this .methodObject = targetClass.getMethod(targetMethod,
186: argTypes);
187: } catch (NoSuchMethodException ex) {
188: // Just rethrow exception if we can't get any match.
189: this .methodObject = findMatchingMethod();
190: if (this .methodObject == null) {
191: throw ex;
192: }
193: }
194: }
195:
196: /**
197: * Resolve the given class name into a Class.
198: * <p>The default implementations uses <code>ClassUtils.forName</code>,
199: * using the thread context class loader.
200: * @param className the class name to resolve
201: * @return the resolved Class
202: * @throws ClassNotFoundException if the class name was invalid
203: */
204: protected Class resolveClassName(String className)
205: throws ClassNotFoundException {
206: return ClassUtils.forName(className);
207: }
208:
209: /**
210: * Find a matching method with the specified name for the specified arguments.
211: * @return a matching method, or <code>null</code> if none
212: * @see #getTargetClass()
213: * @see #getTargetMethod()
214: * @see #getArguments()
215: */
216: protected Method findMatchingMethod() {
217: String targetMethod = getTargetMethod();
218: Object[] arguments = getArguments();
219: int argCount = arguments.length;
220:
221: Method[] candidates = getTargetClass().getMethods();
222: Method matchingMethod = null;
223: int numberOfMatchingMethods = 0;
224:
225: for (int i = 0; i < candidates.length; i++) {
226: Method candidate = candidates[i];
227: Class[] paramTypes = candidate.getParameterTypes();
228: int paramCount = paramTypes.length;
229: if (candidate.getName().equals(targetMethod)
230: && paramCount == argCount) {
231: boolean match = true;
232: for (int j = 0; j < paramCount && match; j++) {
233: match = match
234: && ClassUtils.isAssignableValue(
235: paramTypes[j], arguments[j]);
236: }
237: if (match) {
238: matchingMethod = candidate;
239: numberOfMatchingMethods++;
240: }
241: }
242: }
243:
244: // Only return matching method if exactly one found.
245: if (numberOfMatchingMethods == 1) {
246: return matchingMethod;
247: } else {
248: return null;
249: }
250: }
251:
252: /**
253: * Return the prepared Method object that will be invoked.
254: * <p>Can for example be used to determine the return type.
255: * @return the prepared Method object (never <code>null</code>)
256: * @throws IllegalStateException if the invoker hasn't been prepared yet
257: * @see #prepare
258: * @see #invoke
259: */
260: public Method getPreparedMethod() throws IllegalStateException {
261: if (this .methodObject == null) {
262: throw new IllegalStateException(
263: "prepare() must be called prior to invoke() on MethodInvoker");
264: }
265: return this .methodObject;
266: }
267:
268: /**
269: * Return whether this invoker has been prepared already,
270: * i.e. whether it allows access to {@link #getPreparedMethod()} already.
271: */
272: public boolean isPrepared() {
273: return (this .methodObject != null);
274: }
275:
276: /**
277: * Invoke the specified method.
278: * <p>The invoker needs to have been prepared before.
279: * @return the object (possibly null) returned by the method invocation,
280: * or <code>null</code> if the method has a void return type
281: * @throws InvocationTargetException if the target method threw an exception
282: * @throws IllegalAccessException if the target method couldn't be accessed
283: * @see #prepare
284: */
285: public Object invoke() throws InvocationTargetException,
286: IllegalAccessException {
287: // In the static case, target will simply be <code>null</code>.
288: Object targetObject = getTargetObject();
289: Method preparedMethod = getPreparedMethod();
290: if (targetObject == null
291: && !Modifier.isStatic(preparedMethod.getModifiers())) {
292: throw new IllegalArgumentException(
293: "Target method must not be non-static without a target");
294: }
295: return preparedMethod.invoke(targetObject, getArguments());
296: }
297:
298: }
|