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 as published by
5    * the Free Software Foundation. This program is distributed in the hope
6    * that it will be useful, but WITHOUT ANY WARRANTY; without even the
7    * 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   * Copyright: 2005
18   *     The copyright to this program is held by it's authors.
19   *
20   * ID: $Id: ReflectionUtil.java 2090 2011-03-07 04:13:05Z dmsmith $
21   */
22  package org.crosswire.common.util;
23  
24  import java.lang.reflect.Constructor;
25  import java.lang.reflect.InvocationTargetException;
26  import java.lang.reflect.Method;
27  
28  /**
29   * Various utilities for calling constructors and methods via introspection.
30   * 
31   * @see gnu.lgpl.License for license details.<br>
32   *      The copyright to this program is held by it's authors.
33   * @author Joe Walker [joe at eireneh dot com]
34   * @author DM Smith [dmsmith555 at yahoo dot com]
35   */
36  public final class ReflectionUtil {
37      /**
38       * Prevent instantiation
39       */
40      private ReflectionUtil() {
41      }
42  
43      /**
44       * Build an object using its default constructor. Note: a constructor that
45       * takes a boolean needs a type of boolean.class, but a parameter of type
46       * Boolean. Likewise for other primitives. If this is needed, do not call
47       * this method.
48       * 
49       * @param className
50       *            the full class name of the object
51       * @return the constructed object
52       * @throws ClassNotFoundException
53       * @throws IllegalAccessException
54       * @throws InstantiationException
55       */
56      public static <T> T construct(String className) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
57          Class<T> clazz = (Class<T>) ClassUtil.forName(className);
58          return clazz.newInstance();
59      }
60  
61      /**
62       * Build an object using the supplied parameters. Note: a constructor that
63       * takes a boolean needs a type of boolean.class, but a parameter of type
64       * Boolean. Likewise for other primitives.
65       * 
66       * @param className
67       *            the full class name of the object
68       * @param params
69       *            the constructor's arguments
70       * @return the built object
71       * @throws ClassNotFoundException
72       * @throws NoSuchMethodException
73       * @throws IllegalAccessException
74       * @throws InvocationTargetException
75       * @throws InstantiationException
76       */
77      public static <T> T construct(String className, Object... params) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException,
78              InvocationTargetException, InstantiationException
79      {
80          Class<?>[] paramTypes = describeParameters(params);
81          Class<T> clazz = (Class<T>) ClassUtil.forName(className);
82          final Constructor<T> c = clazz.getConstructor(paramTypes);
83          return c.newInstance(params);
84      }
85  
86      /**
87       * Build an object using the supplied parameters.
88       * 
89       * @param className
90       *            the full class name of the object
91       * @param params
92       *            the constructor's arguments
93       * @param paramTypes
94       *            the types of the parameters
95       * @return the built object
96       * @throws ClassNotFoundException
97       * @throws NoSuchMethodException
98       * @throws IllegalAccessException
99       * @throws InvocationTargetException
100      * @throws InstantiationException
101      */
102     public static <T> T construct(String className, Object[] params, Class<?>[] paramTypes) throws ClassNotFoundException, NoSuchMethodException,
103             IllegalAccessException, InvocationTargetException, InstantiationException
104     {
105         Class<?>[] calledTypes = paramTypes;
106         if (calledTypes == null) {
107             calledTypes = describeParameters(params);
108         }
109         Class<T> clazz = (Class<T>) ClassUtil.forName(className);
110         final Constructor<T> c = clazz.getConstructor(calledTypes);
111         return c.newInstance(params);
112     }
113 
114     /**
115      * Call a method on a class given a sting
116      * 
117      * @param base
118      *            The object to invoke a method on
119      * @param methodName
120      *            The text of the invocation, for example "getName"
121      * @param params
122      *            For example new Object[] { ...}
123      */
124     public static Object invoke(Object base, String methodName, Object... params) throws NoSuchMethodException, IllegalAccessException,
125             InvocationTargetException
126     {
127         Class<?> clazz = base.getClass();
128         return invoke(clazz, base, methodName, params);
129     }
130 
131     /**
132      * Call a static method on a class given a string
133      * 
134      * @param call
135      *            The text of the invocation, for example
136      *            "java.lang.String.getName"
137      * @param params
138      *            For example new Object[] { ...}
139      */
140     public static Object invoke(String call, Object... params) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException,
141             InvocationTargetException
142     {
143         // Split the call into class name and method name
144         int lastDot = call.lastIndexOf('.');
145         String className = call.substring(0, lastDot);
146         String methodName = call.substring(lastDot + 1);
147         Class<?> clazz = ClassUtil.forName(className);
148         return invoke(clazz, clazz, methodName, params);
149     }
150 
151     /**
152      * Call a method on an object, or statically, with the supplied parameters.
153      * 
154      * Note: a method that takes a boolean needs a type of boolean.class, but a
155      * parameter of type Boolean. Likewise for other primitives. If this is
156      * needed, do not call this method.
157      * 
158      * @param clazz
159      *            the class of the object
160      * @param obj
161      *            the object having the method, or null to call a static method
162      * @param methodName
163      *            the method to be called
164      * @param params
165      *            the parameters
166      * @return whatever the method returns
167      * @throws NoSuchMethodException
168      * @throws IllegalAccessException
169      * @throws InvocationTargetException
170      */
171     public static <T> Object invoke(Class<T> clazz, Object obj, String methodName, Object... params) throws NoSuchMethodException, IllegalAccessException,
172             InvocationTargetException
173     {
174         return invoke(clazz, obj, methodName, params, null);
175     }
176 
177     /**
178      * Call a method on an object, or statically, with the supplied parameters.
179      * 
180      * Note: a method that takes a boolean needs a type of boolean.class, but a
181      * parameter of type Boolean. Likewise for other primitives.
182      * 
183      * @param clazz
184      *            the class of the object
185      * @param obj
186      *            the object having the method, or null to call a static method
187      * @param methodName
188      *            the method to be called
189      * @param params
190      *            the parameters
191      * @param paramTypes
192      *            the types of each of the parameters
193      * @return whatever the method returns
194      * @throws NoSuchMethodException
195      * @throws IllegalAccessException
196      * @throws InvocationTargetException
197      */
198     public static <T> Object invoke(Class<T> clazz, Object obj, String methodName, Object[] params, Class<?>[] paramTypes) throws NoSuchMethodException,
199             IllegalAccessException, InvocationTargetException
200     {
201         Class<?>[] calledTypes = paramTypes;
202         if (calledTypes == null) {
203             calledTypes = describeParameters(params);
204         }
205         return getMethod(clazz, methodName, calledTypes).invoke(obj, params);
206     }
207 
208     /**
209      * Construct a parallel array of class objects for each element in params.
210      * 
211      * @param params
212      *            the types to describe
213      * @return the parallel array of class objects
214      */
215     private static Class<?>[] describeParameters(Object... params) {
216         Class<?>[] calledTypes = new Class[params.length];
217         for (int i = 0; i < params.length; i++) {
218             Class<?> clazz = params[i].getClass();
219             if (clazz.equals(Boolean.class)) {
220                 clazz = boolean.class;
221             }
222             calledTypes[i] = clazz;
223         }
224         return calledTypes;
225     }
226 
227     private static <T> Method getMethod(Class<T> clazz, String methodName, Class<?>[] calledTypes) throws NoSuchMethodException {
228         // The bad news is that we can't use something like:
229         // clazz.getMethod(methodNames, called_types);
230         // because it does not cope with inheritance (at least in the MVM)
231         // so we have to search ourselves...
232         Method[] testMethods = clazz.getMethods();
233         outer: for (int i = 0; i < testMethods.length; i++) {
234             // This this the right method name?
235             if (!testMethods[i].getName().equals(methodName)) {
236                 continue outer;
237             }
238 
239             // The right number of params
240             Class<?>[] testTypes = testMethods[i].getParameterTypes();
241             if (testTypes.length != calledTypes.length) {
242                 continue;
243             }
244 
245             // Of the right types?
246             for (int j = 0; j < testTypes.length; j++) {
247                 if (!testTypes[j].isAssignableFrom(calledTypes[j])) {
248                     continue outer;
249                 }
250             }
251 
252             // So this is a match
253             return testMethods[i];
254         }
255 
256         throw new NoSuchMethodException(methodName);
257     }
258 }
259