001 /***************************************************************************** 002 * Copyright (C) PicoContainer Organization. All rights reserved. * 003 * ------------------------------------------------------------------------- * 004 * The software in this package is published under the terms of the BSD * 005 * style license a copy of which has been included with this distribution in * 006 * the LICENSE.txt file. * 007 * * 008 * Original code by Centerline Computers, Inc. * 009 *****************************************************************************/ 010 package org.picocontainer.gems.util; 011 012 import java.lang.reflect.InvocationTargetException; 013 import java.lang.reflect.Method; 014 import java.lang.reflect.Modifier; 015 import java.util.Arrays; 016 017 /** 018 * The DelegateMethod class has been designed in the hope of providing easier 019 * access to methods invoked via reflection. Sample: 020 * 021 * <pre> 022 * //Sample Map 023 * HashMap<String, String> testMap = new HashMap<String, String>(); 024 * testMap.put("a", "A"); 025 * 026 * //Create delegate method that calls the 'clear' method for HashMap. 027 * DelegateMethod<Map, Void> method = new DelegateMethod<Map, Void>(Map.class, 028 * "clear"); 029 * 030 * //Invokes clear() on the HashMap. 031 * method.invoke(testMap); 032 * </pre> 033 * 034 * <p> 035 * Good uses of this object are for lazy invocation of a method and integrating 036 * reflection with a vistor pattern. 037 * </p> 038 * 039 * @author Michael Rimov 040 */ 041 public class DelegateMethod<TARGET_TYPE, RETURN_TYPE> { 042 043 /** 044 * Arguments for the method invocation. 045 */ 046 private final Object[] args; 047 048 /** 049 * The method to be invoked. 050 */ 051 private final Method method; 052 053 /** 054 * Constructs a delegate method object that will invoke method 055 * <em>methodName</em> on class <em>type</em> with the parameters 056 * specified. The object automatically searches for a suitable object to be 057 * invoked. 058 * <p> 059 * Note that this version simply grabs the 060 * <em>first<em> method that fits the parameter criteria with 061 * the specific name. You may need to be careful if use extensive overloading.</p> 062 * <p>To specify the exact types in the method. 063 * @param type the class of the object that should be invoked. 064 * @param methodName the name of the method that will be invoked. 065 * @param parameters the parameters to be used. 066 * @throws NoSuchMethodRuntimeException if the method is not found or parameters that match cannot be found. 067 */ 068 public DelegateMethod(final Class<TARGET_TYPE> type, 069 final String methodName, final Object... parameters) 070 throws NoSuchMethodRuntimeException { 071 this.args = parameters; 072 this.method = findMatchingMethod(type.getMethods(), methodName, 073 parameters); 074 075 if (method == null) { 076 throw new NoSuchMethodRuntimeException("Could not find method " 077 + methodName + " in type " + type.getName()); 078 } 079 } 080 081 /** 082 * Constructs a DelegateMethod object with very specific argument types. 083 * 084 * @param type 085 * the type of the class to be examined for reflection. 086 * @param methodName 087 * the name of the method to be invoked. 088 * @param paramTypes 089 * specific parameter types for the method to be found. 090 * @param parameters 091 * the parameters for method invocation. 092 * @throws NoSuchMethodRuntimeException 093 * if the method is not found. 094 */ 095 public DelegateMethod(final Class<?> type, final String methodName, 096 final Class<?>[] paramTypes, final Object... parameters) 097 throws NoSuchMethodRuntimeException { 098 this.args = parameters; 099 try { 100 this.method = type.getMethod(methodName, paramTypes); 101 } catch (NoSuchMethodException e) { 102 throw new NoSuchMethodRuntimeException("Could not find method " 103 + methodName + " in type " + type.getName()); 104 } 105 } 106 107 /** 108 * Constructs a method delegate with an explicit Method object. 109 * 110 * @param targetMethod 111 * @param parameters 112 */ 113 public DelegateMethod(final Method targetMethod, final Object... parameters) { 114 this.args = parameters; 115 this.method = targetMethod; 116 } 117 118 /** 119 * Locates a method that fits the given parameter types. 120 * 121 * @param methods 122 * @param methodName 123 * @param parameters 124 * @return 125 */ 126 private Method findMatchingMethod(final Method[] methods, 127 final String methodName, final Object[] parameters) { 128 129 // Get parameter types. 130 Class<?>[] paramTypes = new Class[parameters.length]; 131 for (int i = 0; i < parameters.length; i++) { 132 if (parameters[i] == null) { 133 paramTypes[i] = NullType.class; 134 } else { 135 paramTypes[i] = parameters[i].getClass(); 136 } 137 } 138 139 for (Method eachMethod : methods) { 140 if (eachMethod.getName().equals(methodName)) { 141 if (isPotentialMatchingArguments(eachMethod, paramTypes)) { 142 return eachMethod; 143 } 144 } 145 } 146 147 return null; 148 } 149 150 /** 151 * Returns true if all parameter types are assignable to the argument type. 152 * 153 * @param eachMethod 154 * the method we're checking. 155 * @param paramTypes 156 * the parameter types provided as constructor arguments. 157 * @return true if the given method is a match given the parameter types. 158 */ 159 private boolean isPotentialMatchingArguments(final Method eachMethod, 160 final Class<?>[] paramTypes) { 161 Class<?>[] argParameters = eachMethod.getParameterTypes(); 162 if (argParameters.length != paramTypes.length) { 163 return false; 164 } 165 166 for (int i = 0; i < paramTypes.length; i++) { 167 if (paramTypes[i].getName().equals(NullType.class.getName())) { 168 // Nulls are allowed for any parameter. 169 continue; 170 } 171 172 if (!argParameters[i].isAssignableFrom(paramTypes[i])) { 173 return false; 174 } 175 } 176 177 return true; 178 } 179 180 /** 181 * Used for invoking static methods on the type passed into the constructor. 182 * 183 * @return the result of the invocation. May be null if the return type is 184 * void. 185 * @throws IllegalArgumentException 186 * if the method being invoked is not static. 187 * @throws IllegalAccessRuntimeException 188 * if the method being invoked is not public. 189 * @throws InvocationTargetRuntimeException 190 * if an exception is thrown within the method being invoked. 191 */ 192 public RETURN_TYPE invoke() throws IllegalArgumentException, 193 IllegalAccessRuntimeException, InvocationTargetRuntimeException { 194 if (!Modifier.isStatic(method.getModifiers())) { 195 throw new IllegalArgumentException("Method " 196 + method.toGenericString() 197 + " is not static. Use invoke(Object) instead."); 198 } 199 200 return invoke(null); 201 } 202 203 @SuppressWarnings("unchecked") 204 private RETURN_TYPE cast(final Object objectToCast) { 205 return (RETURN_TYPE) objectToCast; 206 } 207 208 /** 209 * Invokes the method specified in the constructor against the target 210 * specified. 211 * 212 * @param <V> 213 * a subclass of the type specified by the object declaration. 214 * This allows Map delegates to operate on HashMaps etc. 215 * @param target 216 * the target object instance to be operated upon. Unless 217 * invoking a static method, this should not be null. 218 * @return the result of the invocation. May be null if the return type is 219 * void. 220 * @throws IllegalArgumentException 221 * if the method being invoked is not static and parameter 222 * target null. 223 * @throws IllegalAccessRuntimeException 224 * if the method being invoked is not public. 225 * @throws InvocationTargetRuntimeException 226 * if an exception is thrown within the method being invoked. 227 */ 228 public <V extends TARGET_TYPE> RETURN_TYPE invoke(final V target) 229 throws IllegalAccessRuntimeException, 230 InvocationTargetRuntimeException { 231 assert args != null; 232 233 if (!Modifier.isStatic(method.getModifiers()) && target == null) { 234 throw new IllegalArgumentException("Method " 235 + method.toGenericString() 236 + " is not static. Use invoke(Object) instead."); 237 } 238 239 RETURN_TYPE result; 240 try { 241 result = cast(method.invoke(target, args)); 242 } catch (IllegalAccessException e) { 243 throw new IllegalAccessRuntimeException("Method " 244 + method.toGenericString() + " is not public.", e); 245 } catch (InvocationTargetException e) { 246 // Unwrap the exception. Should save confusing duplicate traces. 247 throw new InvocationTargetRuntimeException( 248 "There was an error invoking " + method.toGenericString(), 249 e.getCause()); 250 } 251 252 return result; 253 } 254 255 /** {@inheritDoc} */ 256 @Override 257 public String toString() { 258 return "DelegateMethod " + method.toGenericString() 259 + " with arguments: " + Arrays.deepToString(args); 260 } 261 262 /** {@inheritDoc} */ 263 @Override 264 public int hashCode() { 265 final int prime = 31; 266 int result = 1; 267 result = prime * result + Arrays.hashCode(args); 268 result = prime * result + ((method == null) ? 0 : method.hashCode()); 269 return result; 270 } 271 272 /** {@inheritDoc} */ 273 @Override 274 @SuppressWarnings("unchecked") 275 public boolean equals(final Object obj) { 276 if (this == obj) { 277 return true; 278 } 279 if (obj == null) { 280 return false; 281 } 282 if (getClass() != obj.getClass()) { 283 return false; 284 } 285 final DelegateMethod other = (DelegateMethod) obj; 286 if (!Arrays.equals(args, other.args)) { 287 return false; 288 } 289 if (method == null) { 290 if (other.method != null) { 291 return false; 292 } 293 } else if (!method.equals(other.method)) { 294 return false; 295 } 296 return true; 297 } 298 299 /** 300 * Retrieves the expected return type of the delegate method. 301 * @return 302 */ 303 public Class<?> getReturnType() { 304 return method.getReturnType(); 305 } 306 307 /** 308 * Placeholder type used for comparing null parameter values. 309 * 310 * @author Michael Rimov 311 */ 312 private static final class NullType { 313 314 /** 315 * This type should never be constructed. 316 */ 317 private NullType() { 318 319 } 320 } 321 322 }