ReflectionUtil.java |
1 /** 2 * Distribution License: 3 * JSword is free software; you can redistribute it and/or modify it under 4 * the terms of the GNU Lesser General Public License, version 2.1 or later 5 * as published by the Free Software Foundation. This program is distributed 6 * in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even 7 * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 8 * See the GNU Lesser General Public License for more details. 9 * 10 * The License is available on the internet at: 11 * http://www.gnu.org/copyleft/lgpl.html 12 * or by writing to: 13 * Free Software Foundation, Inc. 14 * 59 Temple Place - Suite 330 15 * Boston, MA 02111-1307, USA 16 * 17 * © CrossWire Bible Society, 2005 - 2016 18 * 19 */ 20 package org.crosswire.common.util; 21 22 import java.lang.reflect.Constructor; 23 import java.lang.reflect.InvocationTargetException; 24 import java.lang.reflect.Method; 25 26 /** 27 * Various utilities for calling constructors and methods via introspection. 28 * 29 * @see gnu.lgpl.License The GNU Lesser General Public License for details. 30 * @author Joe Walker 31 * @author DM Smith 32 */ 33 public final class ReflectionUtil { 34 /** 35 * Prevent instantiation 36 */ 37 private ReflectionUtil() { 38 } 39 40 /** 41 * Build an object using its default constructor. Note: a constructor that 42 * takes a boolean needs a type of boolean.class, but a parameter of type 43 * Boolean. Likewise for other primitives. If this is needed, do not call 44 * this method. 45 * 46 * @param <T> the type of the object to construct 47 * @param className 48 * the full class name of the object 49 * @return the constructed object 50 * @throws ClassNotFoundException if the class is not found 51 * @throws InstantiationException 52 * if this {@code data} represents an abstract class, 53 * an interface, an array class, a primitive type, or void; 54 * or if the class has no nullary constructor; 55 * or if the instantiation fails for some other reason. 56 * @throws IllegalAccessException if the class or its nullary 57 * constructor is not accessible. 58 */ 59 public static <T> T construct(String className) throws ClassNotFoundException, InstantiationException, IllegalAccessException { 60 Class<T> clazz = (Class<T>) ClassUtil.forName(className); 61 return clazz.newInstance(); 62 } 63 64 /** 65 * Build an object using the supplied parameters. Note: a constructor that 66 * takes a boolean needs a type of boolean.class, but a parameter of type 67 * Boolean. Likewise for other primitives. 68 * 69 * @param <T> the type of the object to construct 70 * @param className 71 * the full class name of the object 72 * @param params 73 * the constructor's arguments 74 * @return the built object 75 * @throws ClassNotFoundException if the class is not found 76 * @throws NoSuchMethodException 77 * the method does not exist 78 * @throws InstantiationException 79 * if this {@code data} represents an abstract class, 80 * an interface, an array class, a primitive type, or void; 81 * or if the class has no nullary constructor; 82 * or if the instantiation fails for some other reason. 83 * @throws IllegalAccessException if the class or its nullary 84 * constructor is not accessible. 85 * @throws InvocationTargetException if the underlying constructor 86 * throws an exception. 87 * @throws InstantiationException 88 * if the class that declares the 89 * underlying constructor represents an abstract class. 90 */ 91 public static <T> T construct(String className, Object... params) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, 92 InvocationTargetException, InstantiationException 93 { 94 Class<T> clazz = (Class<T>) ClassUtil.forName(className); 95 return construct(clazz, params); 96 } 97 98 /** 99 * Build an object using the supplied parameters. Note: a constructor that 100 * takes a boolean needs a type of boolean.class, but a parameter of type 101 * Boolean. Likewise for other primitives. 102 * 103 * @param <T> the type of the object to construct 104 * @param clazz 105 * the class of the object 106 * @param params 107 * the constructor's arguments 108 * @return the built object 109 * @throws NoSuchMethodException 110 * the method does not exist 111 * @throws InstantiationException 112 * if the class that declares the 113 * underlying constructor represents an abstract class. 114 * @throws IllegalAccessException 115 * if this {@code Constructor} object 116 * is enforcing Java language access control and the underlying 117 * constructor is inaccessible. 118 * @throws InvocationTargetException if the underlying constructor 119 * throws an exception. 120 */ 121 public static <T> T construct(final Class<T> clazz, final Object... params) throws NoSuchMethodException, InstantiationException, IllegalAccessException, 122 InvocationTargetException 123 { 124 Class<?>[] paramTypes = describeParameters(params); 125 final Constructor<T> c = clazz.getConstructor(paramTypes); 126 return c.newInstance(params); 127 } 128 129 /** 130 * Build an object using the supplied parameters. 131 * 132 * @param <T> the type of the object to construct 133 * @param className 134 * the full class name of the object 135 * @param params 136 * the constructor's arguments 137 * @param paramTypes 138 * the types of the parameters 139 * @return the built object 140 * @throws ClassNotFoundException if the class is not found 141 * @throws NoSuchMethodException 142 * the method does not exist 143 * @throws InstantiationException 144 * if the class that declares the 145 * underlying constructor represents an abstract class. 146 * @throws IllegalAccessException 147 * if this {@code Constructor} object 148 * is enforcing Java language access control and the underlying 149 * constructor is inaccessible. 150 * @throws InvocationTargetException if the underlying constructor 151 * throws an exception. 152 */ 153 public static <T> T construct(String className, Object[] params, Class<?>[] paramTypes) throws ClassNotFoundException, NoSuchMethodException, 154 IllegalAccessException, InvocationTargetException, InstantiationException 155 { 156 Class<?>[] calledTypes = paramTypes; 157 if (calledTypes == null) { 158 calledTypes = describeParameters(params); 159 } 160 Class<T> clazz = (Class<T>) ClassUtil.forName(className); 161 final Constructor<T> c = clazz.getConstructor(calledTypes); 162 return c.newInstance(params); 163 } 164 165 /** 166 * Call a method on a class given a sting 167 * 168 * @param base 169 * The object to invoke a method on 170 * @param methodName 171 * The text of the invocation, for example "getName" 172 * @param params 173 * For example new Object[] { ...} 174 * @return whatever the method returs 175 * @throws NoSuchMethodException 176 * the method does not exist 177 * @throws IllegalAccessException 178 * if this {@code Constructor} object 179 * is enforcing Java language access control and the underlying 180 * constructor is inaccessible. 181 * @throws InvocationTargetException if the underlying constructor 182 * throws an exception. 183 */ 184 public static Object invoke(Object base, String methodName, Object... params) throws NoSuchMethodException, IllegalAccessException, 185 InvocationTargetException 186 { 187 Class<?> clazz = base.getClass(); 188 return invoke(clazz, base, methodName, params); 189 } 190 191 /** 192 * Call a static method on a class given a string 193 * 194 * @param call 195 * The text of the invocation, for example 196 * "java.lang.String.getName" 197 * @param params 198 * For example new Object[] { ...} 199 * @return whatever the method returs 200 * @throws ClassNotFoundException if the class is not found 201 * @throws NoSuchMethodException 202 * the method does not exist 203 * @throws IllegalAccessException 204 * if this {@code Constructor} object 205 * is enforcing Java language access control and the underlying 206 * constructor is inaccessible. 207 * @throws InvocationTargetException if the underlying constructor 208 * throws an exception. 209 */ 210 public static Object invoke(String call, Object... params) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, 211 InvocationTargetException 212 { 213 // Split the call into class name and method name 214 int lastDot = call.lastIndexOf('.'); 215 String className = call.substring(0, lastDot); 216 String methodName = call.substring(lastDot + 1); 217 Class<?> clazz = ClassUtil.forName(className); 218 return invoke(clazz, clazz, methodName, params); 219 } 220 221 /** 222 * Call a method on an object, or statically, with the supplied parameters. 223 * 224 * Note: a method that takes a boolean needs a type of boolean.class, but a 225 * parameter of type Boolean. Likewise for other primitives. If this is 226 * needed, do not call this method. 227 * 228 * @param <T> the type of the object to construct 229 * @param clazz 230 * the class of the object 231 * @param obj 232 * the object having the method, or null to call a static method 233 * @param methodName 234 * the method to be called 235 * @param params 236 * the parameters 237 * @return whatever the method returns 238 * @throws NoSuchMethodException 239 * the method does not exist 240 * @throws IllegalAccessException 241 * if this {@code Constructor} object 242 * is enforcing Java language access control and the underlying 243 * constructor is inaccessible. 244 * @throws InvocationTargetException if the underlying constructor 245 * throws an exception. 246 */ 247 public static <T> Object invoke(Class<T> clazz, Object obj, String methodName, Object... params) throws NoSuchMethodException, IllegalAccessException, 248 InvocationTargetException 249 { 250 return invoke(clazz, obj, methodName, params, null); 251 } 252 253 /** 254 * Call a method on an object, or statically, with the supplied parameters. 255 * 256 * Note: a method that takes a boolean needs a type of boolean.class, but a 257 * parameter of type Boolean. Likewise for other primitives. 258 * 259 * @param <T> the type of the object to construct 260 * @param clazz 261 * the class of the object 262 * @param obj 263 * the object having the method, or null to call a static method 264 * @param methodName 265 * the method to be called 266 * @param params 267 * the parameters 268 * @param paramTypes 269 * the types of each of the parameters 270 * @return whatever the method returns 271 * @throws NoSuchMethodException 272 * the method does not exist 273 * @throws IllegalAccessException 274 * if this {@code Constructor} object 275 * is enforcing Java language access control and the underlying 276 * constructor is inaccessible. 277 * @throws InvocationTargetException if the underlying constructor 278 * throws an exception. 279 */ 280 public static <T> Object invoke(Class<T> clazz, Object obj, String methodName, Object[] params, Class<?>[] paramTypes) throws NoSuchMethodException, 281 IllegalAccessException, InvocationTargetException 282 { 283 Class<?>[] calledTypes = paramTypes; 284 if (calledTypes == null) { 285 calledTypes = describeParameters(params); 286 } 287 return getMethod(clazz, methodName, calledTypes).invoke(obj, params); 288 } 289 290 /** 291 * Construct a parallel array of class objects for each element in params. 292 * 293 * @param params 294 * the types to describe 295 * @return the parallel array of class objects 296 */ 297 private static Class<?>[] describeParameters(Object... params) { 298 Class<?>[] calledTypes = new Class[params.length]; 299 for (int i = 0; i < params.length; i++) { 300 Class<?> clazz = params[i].getClass(); 301 if (clazz.equals(Boolean.class)) { 302 clazz = boolean.class; 303 } 304 calledTypes[i] = clazz; 305 } 306 return calledTypes; 307 } 308 309 private static <T> Method getMethod(Class<T> clazz, String methodName, Class<?>[] calledTypes) throws NoSuchMethodException { 310 // The bad news is that we can't use something like: 311 // clazz.getMethod(methodNames, called_types); 312 // because it does not cope with inheritance (at least in the MVM) 313 // so we have to search ourselves... 314 Method[] testMethods = clazz.getMethods(); 315 outer: for (int i = 0; i < testMethods.length; i++) { 316 // This this the right method name? 317 if (!testMethods[i].getName().equals(methodName)) { 318 continue outer; 319 } 320 321 // The right number of params 322 Class<?>[] testTypes = testMethods[i].getParameterTypes(); 323 if (testTypes.length != calledTypes.length) { 324 continue; 325 } 326 327 // Of the right types? 328 for (int j = 0; j < testTypes.length; j++) { 329 if (!testTypes[j].isAssignableFrom(calledTypes[j])) { 330 continue outer; 331 } 332 } 333 334 // So this is a match 335 return testMethods[i]; 336 } 337 338 throw new NoSuchMethodException(methodName); 339 } 340 } 341