All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.picocontainer.gems.util.DelegateMethod Maven / Gradle / Ivy

The newest version!
/*****************************************************************************
 * Copyright (C) PicoContainer Organization. All rights reserved.            *
 * ------------------------------------------------------------------------- *
 * The software in this package is published under the terms of the BSD      *
 * style license a copy of which has been included with this distribution in *
 * the LICENSE.txt file.                                                     *
 *                                                                           *
 * Original code by Centerline Computers, Inc.                               *
 *****************************************************************************/
package org.picocontainer.gems.util;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;

/**
 * The DelegateMethod class has been designed in the hope of providing easier
 * access to methods invoked via reflection. Sample:
 * 
 * 
 * //Sample Map
 * HashMap<String, String> testMap = new HashMap<String, String>();
 * testMap.put("a", "A");
 * 
 * //Create delegate method that calls the 'clear' method for HashMap.
 * DelegateMethod<Map, Void> method = new DelegateMethod<Map, Void>(Map.class,
 * 		"clear");
 * 
 * //Invokes clear() on the HashMap.
 * method.invoke(testMap);
 * 
* *

* Good uses of this object are for lazy invocation of a method and integrating * reflection with a vistor pattern. *

* * @author Michael Rimov */ public class DelegateMethod { /** * Arguments for the method invocation. */ private final Object[] args; /** * The method to be invoked. */ private final Method method; /** * Constructs a delegate method object that will invoke method * methodName on class type with the parameters * specified. The object automatically searches for a suitable object to be * invoked. *

* Note that this version simply grabs the * first method that fits the parameter criteria with * the specific name. You may need to be careful if use extensive overloading.

*

To specify the exact types in the method. * @param type the class of the object that should be invoked. * @param methodName the name of the method that will be invoked. * @param parameters the parameters to be used. * @throws NoSuchMethodRuntimeException if the method is not found or parameters that match cannot be found. */ public DelegateMethod(final Class type, final String methodName, final Object... parameters) throws NoSuchMethodRuntimeException { this.args = parameters; this.method = findMatchingMethod(type.getMethods(), methodName, parameters); if (method == null) { throw new NoSuchMethodRuntimeException("Could not find method " + methodName + " in type " + type.getName()); } } /** * Constructs a DelegateMethod object with very specific argument types. * * @param type * the type of the class to be examined for reflection. * @param methodName * the name of the method to be invoked. * @param paramTypes * specific parameter types for the method to be found. * @param parameters * the parameters for method invocation. * @throws NoSuchMethodRuntimeException * if the method is not found. */ public DelegateMethod(final Class type, final String methodName, final Class[] paramTypes, final Object... parameters) throws NoSuchMethodRuntimeException { this.args = parameters; try { this.method = type.getMethod(methodName, paramTypes); } catch (NoSuchMethodException e) { throw new NoSuchMethodRuntimeException("Could not find method " + methodName + " in type " + type.getName()); } } /** * Constructs a method delegate with an explicit Method object. * * @param targetMethod * @param parameters */ public DelegateMethod(final Method targetMethod, final Object... parameters) { this.args = parameters; this.method = targetMethod; } /** * Locates a method that fits the given parameter types. * * @param methods * @param methodName * @param parameters * @return */ private Method findMatchingMethod(final Method[] methods, final String methodName, final Object[] parameters) { // Get parameter types. Class[] paramTypes = new Class[parameters.length]; for (int i = 0; i < parameters.length; i++) { if (parameters[i] == null) { paramTypes[i] = NullType.class; } else { paramTypes[i] = parameters[i].getClass(); } } for (Method eachMethod : methods) { if (eachMethod.getName().equals(methodName)) { if (isPotentialMatchingArguments(eachMethod, paramTypes)) { return eachMethod; } } } return null; } /** * Returns true if all parameter types are assignable to the argument type. * * @param eachMethod * the method we're checking. * @param paramTypes * the parameter types provided as constructor arguments. * @return true if the given method is a match given the parameter types. */ private boolean isPotentialMatchingArguments(final Method eachMethod, final Class[] paramTypes) { Class[] argParameters = eachMethod.getParameterTypes(); if (argParameters.length != paramTypes.length) { return false; } for (int i = 0; i < paramTypes.length; i++) { if (paramTypes[i].getName().equals(NullType.class.getName())) { // Nulls are allowed for any parameter. continue; } if (!argParameters[i].isAssignableFrom(paramTypes[i])) { return false; } } return true; } /** * Used for invoking static methods on the type passed into the constructor. * * @return the result of the invocation. May be null if the return type is * void. * @throws IllegalArgumentException * if the method being invoked is not static. * @throws IllegalAccessRuntimeException * if the method being invoked is not public. * @throws InvocationTargetRuntimeException * if an exception is thrown within the method being invoked. */ public RETURN_TYPE invoke() throws IllegalArgumentException, IllegalAccessRuntimeException, InvocationTargetRuntimeException { if (!Modifier.isStatic(method.getModifiers())) { throw new IllegalArgumentException("Method " + method.toGenericString() + " is not static. Use invoke(Object) instead."); } return invoke(null); } @SuppressWarnings("unchecked") private RETURN_TYPE cast(final Object objectToCast) { return (RETURN_TYPE) objectToCast; } /** * Invokes the method specified in the constructor against the target * specified. * * @param * a subclass of the type specified by the object declaration. * This allows Map delegates to operate on HashMaps etc. * @param target * the target object instance to be operated upon. Unless * invoking a static method, this should not be null. * @return the result of the invocation. May be null if the return type is * void. * @throws IllegalArgumentException * if the method being invoked is not static and parameter * target null. * @throws IllegalAccessRuntimeException * if the method being invoked is not public. * @throws InvocationTargetRuntimeException * if an exception is thrown within the method being invoked. */ public RETURN_TYPE invoke(final V target) throws IllegalAccessRuntimeException, InvocationTargetRuntimeException { assert args != null; if (!Modifier.isStatic(method.getModifiers()) && target == null) { throw new IllegalArgumentException("Method " + method.toGenericString() + " is not static. Use invoke(Object) instead."); } RETURN_TYPE result; try { result = cast(method.invoke(target, args)); } catch (IllegalAccessException e) { throw new IllegalAccessRuntimeException("Method " + method.toGenericString() + " is not public.", e); } catch (InvocationTargetException e) { // Unwrap the exception. Should save confusing duplicate traces. throw new InvocationTargetRuntimeException( "There was an error invoking " + method.toGenericString(), e.getCause()); } return result; } /** {@inheritDoc} */ @Override public String toString() { return "DelegateMethod " + method.toGenericString() + " with arguments: " + Arrays.deepToString(args); } /** {@inheritDoc} */ @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + Arrays.hashCode(args); result = prime * result + ((method == null) ? 0 : method.hashCode()); return result; } /** {@inheritDoc} */ @Override @SuppressWarnings("unchecked") public boolean equals(final Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } final DelegateMethod other = (DelegateMethod) obj; if (!Arrays.equals(args, other.args)) { return false; } if (method == null) { if (other.method != null) { return false; } } else if (!method.equals(other.method)) { return false; } return true; } /** * Retrieves the expected return type of the delegate method. * @return */ public Class getReturnType() { return method.getReturnType(); } /** * Placeholder type used for comparing null parameter values. * * @author Michael Rimov */ private static final class NullType { /** * This type should never be constructed. */ private NullType() { } } }