| 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