001: /*
002: * Copyright 2004-2006 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.compass.core.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 to specify a method to invoke in a
025: * declarative fashion, be it static or non-static.
026: *
027: * <p>Usage: Specify targetClass/targetMethod respectively
028: * targetObject/targetMethod, optionally specify arguments,
029: * prepare the invoker. Afterwards, you can invoke the method
030: * any number of times.
031: *
032: * <p>Typically not used directly but via its subclasses
033: * MethodInvokingFactoryBean and MethodInvokingJobDetailFactoryBean.
034: *
035: * @author kimchy
036: * @see #prepare
037: * @see #invoke
038: */
039: public class MethodInvoker {
040:
041: private Class targetClass;
042:
043: private Object targetObject;
044:
045: private String targetMethod;
046:
047: private Object[] arguments;
048:
049: // the method we will call
050: private Method methodObject;
051:
052: // booleans marker if the method is static
053: private boolean isStatic;
054:
055: /**
056: * Set the target class on which to call the target method.
057: * Only necessary when the target method is static; else,
058: * a target object needs to be specified anyway.
059: * @see #setTargetObject
060: * @see #setTargetMethod
061: */
062: public void setTargetClass(Class targetClass) {
063: this .targetClass = targetClass;
064: }
065:
066: /**
067: * Return the target class on which to call the target method.
068: */
069: public Class getTargetClass() {
070: return targetClass;
071: }
072:
073: /**
074: * Set the target object on which to call the target method.
075: * Only necessary when the target method is not static;
076: * else, a target class is sufficient.
077: * @see #setTargetClass
078: * @see #setTargetMethod
079: */
080: public void setTargetObject(Object targetObject) {
081: this .targetObject = targetObject;
082: if (targetObject != null) {
083: this .targetClass = targetObject.getClass();
084: }
085: }
086:
087: /**
088: * Return the target object on which to call the target method.
089: */
090: public Object getTargetObject() {
091: return targetObject;
092: }
093:
094: /**
095: * Set the name of the method to be invoked.
096: * Refers to either a static method or a non-static method,
097: * depending on a target object being set.
098: * @see #setTargetClass
099: * @see #setTargetObject
100: */
101: public void setTargetMethod(String targetMethod) {
102: this .targetMethod = targetMethod;
103: }
104:
105: /**
106: * Return the name of the method to be invoked.
107: */
108: public String getTargetMethod() {
109: return targetMethod;
110: }
111:
112: /**
113: * Set a fully qualified static method name to invoke,
114: * e.g. "example.MyExampleClass.myExampleMethod".
115: * Convenient alternative to specifying targetClass and targetMethod.
116: * @see #setTargetClass
117: * @see #setTargetMethod
118: */
119: public void setStaticMethod(String staticMethod)
120: throws ClassNotFoundException {
121: int lastDotIndex = staticMethod.lastIndexOf('.');
122: if (lastDotIndex == -1 || lastDotIndex == staticMethod.length()) {
123: throw new IllegalArgumentException(
124: "staticMethod must be a fully qualified class plus method name: "
125: + "e.g. 'example.MyExampleClass.myExampleMethod'");
126: }
127: String className = staticMethod.substring(0, lastDotIndex);
128: String methodName = staticMethod.substring(lastDotIndex + 1);
129: setTargetClass(Class.forName(className, true, Thread
130: .currentThread().getContextClassLoader()));
131: setTargetMethod(methodName);
132: }
133:
134: /**
135: * Set arguments for the method invocation. If this property is not set,
136: * or the Object array is of length 0, a method with no arguments is assumed.
137: */
138: public void setArguments(Object[] arguments) {
139: this .arguments = arguments;
140: }
141:
142: /**
143: * Retrun the arguments for the method invocation.
144: */
145: public Object[] getArguments() {
146: return arguments;
147: }
148:
149: /**
150: * Prepare the specified method.
151: * The method can be invoked any number of times afterwards.
152: * @see #getPreparedMethod
153: * @see #invoke
154: */
155: public MethodInvoker prepare() throws ClassNotFoundException,
156: NoSuchMethodException {
157: if (this .targetClass == null) {
158: throw new IllegalArgumentException(
159: "Either targetClass or targetObject is required");
160: }
161: if (this .targetMethod == null) {
162: throw new IllegalArgumentException(
163: "targetMethod is required");
164: }
165:
166: if (this .arguments == null) {
167: this .arguments = new Object[0];
168: }
169:
170: Class[] argTypes = new Class[this .arguments.length];
171: for (int i = 0; i < this .arguments.length; ++i) {
172: argTypes[i] = (this .arguments[i] != null ? this .arguments[i]
173: .getClass()
174: : Object.class);
175: }
176:
177: // Try to get the exact method first.
178: try {
179: this .methodObject = this .targetClass.getMethod(
180: this .targetMethod, argTypes);
181: } catch (NoSuchMethodException ex) {
182: // Just rethrow exception if we can't get any match.
183: this .methodObject = findMatchingMethod();
184: if (this .methodObject == null) {
185: throw ex;
186: }
187: }
188:
189: isStatic = Modifier.isStatic(this .methodObject.getModifiers());
190:
191: return this ;
192: }
193:
194: /**
195: * Find a matching method with the specified name for the specified arguments.
196: * @return a matching method, or <code>null</code> if none
197: * @see #getTargetClass()
198: * @see #getTargetMethod()
199: * @see #getArguments()
200: */
201: protected Method findMatchingMethod() {
202: Method[] candidates = getTargetClass().getMethods();
203: int argCount = getArguments().length;
204: Method matchingMethod = null;
205: int numberOfMatchingMethods = 0;
206:
207: for (int i = 0; i < candidates.length; i++) {
208: // Check if the inspected method has the correct name and number of parameters.
209: if (candidates[i].getName().equals(getTargetMethod())
210: && candidates[i].getParameterTypes().length == argCount) {
211: matchingMethod = candidates[i];
212: numberOfMatchingMethods++;
213: }
214: }
215:
216: // Only return matching method if exactly one found.
217: if (numberOfMatchingMethods == 1) {
218: return matchingMethod;
219: } else {
220: return null;
221: }
222: }
223:
224: /**
225: * Return the prepared Method object that will be invoker.
226: * Can for example be used to determine the return type.
227: * @see #prepare
228: * @see #invoke
229: */
230: public Method getPreparedMethod() {
231: return this .methodObject;
232: }
233:
234: /**
235: * Invoke the specified method.
236: * The invoker needs to have been prepared before.
237: * @return the object (possibly null) returned by the method invocation,
238: * or <code>null</code> if the method has a void return type
239: * @see #prepare
240: */
241: public Object invoke() throws InvocationTargetException,
242: IllegalAccessException {
243: if (this .methodObject == null) {
244: throw new IllegalStateException(
245: "prepare() must be called prior to invoke() on MethodInvoker");
246: }
247: // In the static case, target will just be <code>null</code>.
248: return this .methodObject.invoke(this .targetObject,
249: this .arguments);
250: }
251:
252: /**
253: * Another invoke option. Here, the target object and the arguments can
254: * be specified after the prerpare method has been called. Note, that the
255: * arguments should be provided before the prepare as well so the method
256: * can be found (or a template of the arguments).
257: */
258: public Object invoke(Object targetObject, Object[] arguments)
259: throws InvocationTargetException, IllegalAccessException {
260: if (this .methodObject == null) {
261: throw new IllegalStateException(
262: "prepare() must be called prior to invoke() on MethodInvoker");
263: }
264: if (targetObject == null && !this .isStatic) {
265: throw new IllegalArgumentException(
266: "Target method must not be non-static without a target");
267: }
268: if (arguments == null) {
269: arguments = new Object[0];
270: }
271: // In the static case, target will just be <code>null</code>.
272: return this.methodObject.invoke(targetObject, arguments);
273: }
274: }
|