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