Save This Page
Home » bundlor-1.0.0.RELEASE » org.springframework » util » [javadoc | source]
    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   
   17   package org.springframework.util;
   18   
   19   import java.lang.reflect.InvocationTargetException;
   20   import java.lang.reflect.Method;
   21   import java.lang.reflect.Modifier;
   22   
   23   /**
   24    * Helper class that allows for specifying a method to invoke in a declarative
   25    * fashion, be it static or non-static.
   26    *
   27    * <p>Usage: Specify "targetClass"/"targetMethod" or "targetObject"/"targetMethod",
   28    * optionally specify arguments, prepare the invoker. Afterwards, you may
   29    * invoke the method any number of times, obtaining the invocation result.
   30    *
   31    * <p>Typically not used directly but via its subclasses
   32    * {@link org.springframework.beans.factory.config.MethodInvokingFactoryBean} and
   33    * {@link org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean}.
   34    *
   35    * @author Colin Sampaleanu
   36    * @author Juergen Hoeller
   37    * @since 19.02.2004
   38    * @see #prepare
   39    * @see #invoke
   40    * @see org.springframework.beans.factory.config.MethodInvokingFactoryBean
   41    * @see org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean
   42    */
   43   public class MethodInvoker {
   44   
   45   	private Class targetClass;
   46   
   47   	private Object targetObject;
   48   
   49   	private String targetMethod;
   50   
   51   	private String staticMethod;
   52   
   53   	private Object[] arguments = new Object[0];
   54   
   55   	/** The method we will call */
   56   	private Method methodObject;
   57   
   58   
   59   	/**
   60   	 * Set the target class on which to call the target method.
   61   	 * Only necessary when the target method is static; else,
   62   	 * a target object needs to be specified anyway.
   63   	 * @see #setTargetObject
   64   	 * @see #setTargetMethod
   65   	 */
   66   	public void setTargetClass(Class targetClass) {
   67   		this.targetClass = targetClass;
   68   	}
   69   
   70   	/**
   71   	 * Return the target class on which to call the target method.
   72   	 */
   73   	public Class getTargetClass() {
   74   		return this.targetClass;
   75   	}
   76   
   77   	/**
   78   	 * Set the target object on which to call the target method.
   79   	 * Only necessary when the target method is not static;
   80   	 * else, a target class is sufficient.
   81   	 * @see #setTargetClass
   82   	 * @see #setTargetMethod
   83   	 */
   84   	public void setTargetObject(Object targetObject) {
   85   		this.targetObject = targetObject;
   86   		if (targetObject != null) {
   87   			this.targetClass = targetObject.getClass();
   88   		}
   89   	}
   90   
   91   	/**
   92   	 * Return the target object on which to call the target method.
   93   	 */
   94   	public Object getTargetObject() {
   95   		return this.targetObject;
   96   	}
   97   
   98   	/**
   99   	 * Set the name of the method to be invoked.
  100   	 * Refers to either a static method or a non-static method,
  101   	 * depending on a target object being set.
  102   	 * @see #setTargetClass
  103   	 * @see #setTargetObject
  104   	 */
  105   	public void setTargetMethod(String targetMethod) {
  106   		this.targetMethod = targetMethod;
  107   	}
  108   
  109   	/**
  110   	 * Return the name of the method to be invoked.
  111   	 */
  112   	public String getTargetMethod() {
  113   		return this.targetMethod;
  114   	}
  115   
  116   	/**
  117   	 * Set a fully qualified static method name to invoke,
  118   	 * e.g. "example.MyExampleClass.myExampleMethod".
  119   	 * Convenient alternative to specifying targetClass and targetMethod.
  120   	 * @see #setTargetClass
  121   	 * @see #setTargetMethod
  122   	 */
  123   	public void setStaticMethod(String staticMethod) {
  124   		this.staticMethod = staticMethod;
  125   	}
  126   
  127   	/**
  128   	 * Set arguments for the method invocation. If this property is not set,
  129   	 * or the Object array is of length 0, a method with no arguments is assumed.
  130   	 */
  131   	public void setArguments(Object[] arguments) {
  132   		this.arguments = (arguments != null ? arguments : new Object[0]);
  133   	}
  134   
  135   	/**
  136   	 * Return the arguments for the method invocation.
  137   	 */
  138   	public Object[] getArguments() {
  139   		return this.arguments;
  140   	}
  141   
  142   
  143   	/**
  144   	 * Prepare the specified method.
  145   	 * The method can be invoked any number of times afterwards.
  146   	 * @see #getPreparedMethod
  147   	 * @see #invoke
  148   	 */
  149   	public void prepare() throws ClassNotFoundException, NoSuchMethodException {
  150   		if (this.staticMethod != null) {
  151   			int lastDotIndex = this.staticMethod.lastIndexOf('.');
  152   			if (lastDotIndex == -1 || 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, lastDotIndex);
  158   			String methodName = this.staticMethod.substring(lastDotIndex + 1);
  159   			this.targetClass = resolveClassName(className);
  160   			this.targetMethod = methodName;
  161   		}
  162   
  163   		Class targetClass = getTargetClass();
  164   		String targetMethod = getTargetMethod();
  165   		if (targetClass == null) {
  166   			throw new IllegalArgumentException("Either 'targetClass' or 'targetObject' is required");
  167   		}
  168   		if (targetMethod == null) {
  169   			throw new IllegalArgumentException("Property 'targetMethod' is required");
  170   		}
  171   
  172   		Object[] arguments = getArguments();
  173   		Class[] argTypes = new Class[arguments.length];
  174   		for (int i = 0; i < arguments.length; ++i) {
  175   			argTypes[i] = (arguments[i] != null ? arguments[i].getClass() : Object.class);
  176   		}
  177   
  178   		// Try to get the exact method first.
  179   		try {
  180   			this.methodObject = targetClass.getMethod(targetMethod, argTypes);
  181   		}
  182   		catch (NoSuchMethodException ex) {
  183   			// Just rethrow exception if we can't get any match.
  184   			this.methodObject = findMatchingMethod();
  185   			if (this.methodObject == null) {
  186   				throw ex;
  187   			}
  188   		}
  189   	}
  190   
  191   	/**
  192   	 * Resolve the given class name into a Class.
  193   	 * <p>The default implementations uses <code>ClassUtils.forName</code>,
  194   	 * using the thread context class loader.
  195   	 * @param className the class name to resolve
  196   	 * @return the resolved Class
  197   	 * @throws ClassNotFoundException if the class name was invalid
  198   	 */
  199   	protected Class resolveClassName(String className) throws ClassNotFoundException {
  200   		return ClassUtils.forName(className);
  201   	}
  202   
  203   	/**
  204   	 * Find a matching method with the specified name for the specified arguments.
  205   	 * @return a matching method, or <code>null</code> if none
  206   	 * @see #getTargetClass()
  207   	 * @see #getTargetMethod()
  208   	 * @see #getArguments()
  209   	 */
  210   	protected Method findMatchingMethod() {
  211   		String targetMethod = getTargetMethod();
  212   		Object[] arguments = getArguments();
  213   		int argCount = arguments.length;
  214   
  215   		Method[] candidates = ReflectionUtils.getAllDeclaredMethods(getTargetClass());
  216   		int minTypeDiffWeight = Integer.MAX_VALUE;
  217   		Method matchingMethod = null;
  218   
  219   		for (int i = 0; i < candidates.length; i++) {
  220   			Method candidate = candidates[i];
  221   			if (candidate.getName().equals(targetMethod)) {
  222   				Class[] paramTypes = candidate.getParameterTypes();
  223   				if (paramTypes.length == argCount) {
  224   					int typeDiffWeight = getTypeDifferenceWeight(paramTypes, arguments);
  225   					if (typeDiffWeight < minTypeDiffWeight) {
  226   						minTypeDiffWeight = typeDiffWeight;
  227   						matchingMethod = candidate;
  228   					}
  229   				}
  230   			}
  231   		}
  232   
  233   		return matchingMethod;
  234   	}
  235   
  236   	/**
  237   	 * Return the prepared Method object that will be invoked.
  238   	 * <p>Can for example be used to determine the return type.
  239   	 * @return the prepared Method object (never <code>null</code>)
  240   	 * @throws IllegalStateException if the invoker hasn't been prepared yet
  241   	 * @see #prepare
  242   	 * @see #invoke
  243   	 */
  244   	public Method getPreparedMethod() throws IllegalStateException {
  245   		if (this.methodObject == null) {
  246   			throw new IllegalStateException("prepare() must be called prior to invoke() on MethodInvoker");
  247   		}
  248   		return this.methodObject;
  249   	}
  250   
  251   	/**
  252   	 * Return whether this invoker has been prepared already,
  253   	 * i.e. whether it allows access to {@link #getPreparedMethod()} already.
  254   	 */
  255   	public boolean isPrepared() {
  256   		return (this.methodObject != null);
  257   	}
  258   
  259   	/**
  260   	 * Invoke the specified method.
  261   	 * <p>The invoker needs to have been prepared before.
  262   	 * @return the object (possibly null) returned by the method invocation,
  263   	 * or <code>null</code> if the method has a void return type
  264   	 * @throws InvocationTargetException if the target method threw an exception
  265   	 * @throws IllegalAccessException if the target method couldn't be accessed
  266   	 * @see #prepare
  267   	 */
  268   	public Object invoke() throws InvocationTargetException, IllegalAccessException {
  269   		// In the static case, target will simply be <code>null</code>.
  270   		Object targetObject = getTargetObject();
  271   		Method preparedMethod = getPreparedMethod();
  272   		if (targetObject == null && !Modifier.isStatic(preparedMethod.getModifiers())) {
  273   			throw new IllegalArgumentException("Target method must not be non-static without a target");
  274   		}
  275   		ReflectionUtils.makeAccessible(preparedMethod);
  276   		return preparedMethod.invoke(targetObject, getArguments());
  277   	}
  278   
  279   
  280   	/**
  281   	 * Algorithm that judges the match between the declared parameter types of a candidate method
  282   	 * and a specific list of arguments that this method is supposed to be invoked with.
  283   	 * <p>Determines a weight that represents the class hierarchy difference between types and
  284   	 * arguments. A direct match, i.e. type Integer -> arg of class Integer, does not increase
  285   	 * the result - all direct matches means weight 0. A match between type Object and arg of
  286   	 * class Integer would increase the weight by 2, due to the superclass 2 steps up in the
  287   	 * hierarchy (i.e. Object) being the last one that still matches the required type Object.
  288   	 * Type Number and class Integer would increase the weight by 1 accordingly, due to the
  289   	 * superclass 1 step up the hierarchy (i.e. Number) still matching the required type Number.
  290   	 * Therefore, with an arg of type Integer, a constructor (Integer) would be preferred to a
  291   	 * constructor (Number) which would in turn be preferred to a constructor (Object).
  292   	 * All argument weights get accumulated.
  293   	 * @param paramTypes the parameter types to match
  294   	 * @param args the arguments to match
  295   	 * @return the accumulated weight for all arguments
  296   	 */
  297   	public static int getTypeDifferenceWeight(Class[] paramTypes, Object[] args) {
  298   		int result = 0;
  299   		for (int i = 0; i < paramTypes.length; i++) {
  300   			if (!ClassUtils.isAssignableValue(paramTypes[i], args[i])) {
  301   				return Integer.MAX_VALUE;
  302   			}
  303   			if (args[i] != null) {
  304   				Class paramType = paramTypes[i];
  305   				Class superClass = args[i].getClass().getSuperclass();
  306   				while (superClass != null) {
  307   					if (paramType.equals(superClass)) {
  308   						result = result + 2;
  309   						superClass = null;
  310   					}
  311   					else if (ClassUtils.isAssignable(paramType, superClass)) {
  312   						result = result + 2;
  313   						superClass = superClass.getSuperclass();
  314   					}
  315   					else {
  316   						superClass = null;
  317   					}
  318   				}
  319   				if (paramType.isInterface()) {
  320   					result = result + 1;
  321   				}
  322   			}
  323   		}
  324   		return result;
  325   	}
  326   
  327   }

Save This Page
Home » bundlor-1.0.0.RELEASE » org.springframework » util » [javadoc | source]