View Javadoc

1   /*
2    * Copyright 2001-2004 The Apache Software Foundation.
3    * 
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * 
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    * 
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */ 
16  
17  package org.apache.commons.beanutils;
18  
19  import java.lang.reflect.Constructor;
20  import java.lang.reflect.InvocationTargetException;
21  import java.lang.reflect.Modifier;
22  
23  /**
24   * <p> Utility reflection methods focussed on constructors, modelled after {@link MethodUtils}. </p>
25   *
26   * <h3>Known Limitations</h3>
27   * <h4>Accessing Public Constructors In A Default Access Superclass</h4>
28   * <p>There is an issue when invoking public constructors contained in a default access superclass.
29   * Reflection locates these constructors fine and correctly assigns them as public.
30   * However, an <code>IllegalAccessException</code> is thrown if the constructors is invoked.</p>
31   *
32   * <p><code>ConstructorUtils</code> contains a workaround for this situation.
33   * It will attempt to call <code>setAccessible</code> on this constructor.
34   * If this call succeeds, then the method can be invoked as normal.
35   * This call will only succeed when the application has sufficient security privilages.
36   * If this call fails then a warning will be logged and the method may fail.</p>
37   *
38   * @author Craig R. McClanahan
39   * @author Ralph Schaer
40   * @author Chris Audley
41   * @author Rey Fran?ois
42   * @author Gregor Ra?man
43   * @author Jan Sorensen
44   * @author Robert Burrell Donkin
45   * @author Rodney Waldhoff
46   * @version $Revision: 1.7 $ $Date: 2004/02/28 13:18:33 $
47   */
48  public class ConstructorUtils {
49  
50      // --------------------------------------------------------- Private Members
51      /** An empty class array */
52      private static final Class[] emptyClassArray = new Class[0];
53      /** An empty object array */
54      private static final Object[] emptyObjectArray = new Object[0];
55  
56      // --------------------------------------------------------- Public Methods
57  
58      /**
59       * <p>Convenience method returning new instance of <code>klazz</code> using a single argument constructor.
60       * The formal parameter type is inferred from the actual values of <code>arg</code>.
61       * See {@link #invokeExactConstructor(Class, Object[], Class[])} for more details.</p>
62       *
63       * <p>The signatures should be assignment compatible.</p>
64       *
65       * @param klass the class to be constructed.
66       * @param arg the actual argument
67       * @return new instance of <code>klazz</code>
68       *
69       * @see #invokeConstructor(java.lang.Class, java.lang.Object[], java.lang.Class[])
70       */
71      public static Object invokeConstructor(Class klass, Object arg)
72          throws
73              NoSuchMethodException,
74              IllegalAccessException,
75              InvocationTargetException,
76              InstantiationException {
77  
78          Object[] args = { arg };
79          return invokeConstructor(klass, args);
80  
81      }
82  
83      /**
84       * <p>Returns new instance of <code>klazz</code> created using the actual arguments <code>args</code>.
85       * The formal parameter types are inferred from the actual values of <code>args</code>.
86       * See {@link #invokeExactConstructor(Class, Object[], Class[])} for more details.</p>
87       *
88       * <p>The signatures should be assignment compatible.</p>
89       *
90       * @param klass the class to be constructed.
91       * @param args actual argument array
92       * @return new instance of <code>klazz</code>
93       *
94       * @see #invokeConstructor(java.lang.Class, java.lang.Object[], java.lang.Class[])
95       */
96      public static Object invokeConstructor(Class klass, Object[] args)
97          throws
98              NoSuchMethodException,
99              IllegalAccessException,
100             InvocationTargetException,
101             InstantiationException {
102 
103         if (null == args) {
104             args = emptyObjectArray;
105         }
106         int arguments = args.length;
107         Class parameterTypes[] = new Class[arguments];
108         for (int i = 0; i < arguments; i++) {
109             parameterTypes[i] = args[i].getClass();
110         }
111         return invokeConstructor(klass, args, parameterTypes);
112 
113     }
114 
115     /**
116      * <p>Returns new instance of <code>klazz</code> created using constructor
117      * with signature <code>parameterTypes</code> and actual arguments <code>args</code>.</p>
118      *
119      * <p>The signatures should be assignment compatible.</p>
120      *
121      * @param klass the class to be constructed.
122      * @param args actual argument array
123      * @param parameterTypes parameter types array
124      * @return new instance of <code>klazz</code>
125      *
126      * @throws NoSuchMethodException if matching constructor cannot be found
127      * @throws IllegalAccessException thrown on the constructor's invocation
128      * @throws InvocationTargetException thrown on the constructor's invocation
129      * @throws InstantiationException thrown on the constructor's invocation
130      * @see Constructor#newInstance
131      */
132     public static Object invokeConstructor(
133         Class klass,
134         Object[] args,
135         Class[] parameterTypes)
136         throws
137             NoSuchMethodException,
138             IllegalAccessException,
139             InvocationTargetException,
140             InstantiationException {
141 
142         if (parameterTypes == null) {
143             parameterTypes = emptyClassArray;
144         }
145         if (args == null) {
146             args = emptyObjectArray;
147         }
148 
149         Constructor ctor =
150             getMatchingAccessibleConstructor(klass, parameterTypes);
151         if (null == ctor) {
152             throw new NoSuchMethodException(
153                 "No such accessible constructor on object: " + klass.getName());
154         }
155         return ctor.newInstance(args);
156     }
157 
158 
159     /**
160      * <p>Convenience method returning new instance of <code>klazz</code> using a single argument constructor.
161      * The formal parameter type is inferred from the actual values of <code>arg</code>.
162      * See {@link #invokeExactConstructor(Class, Object[], Class[])} for more details.</p>
163      *
164      * <p>The signatures should match exactly.</p>
165      *
166      * @param klass the class to be constructed.
167      * @param arg the actual argument
168      * @return new instance of <code>klazz</code>
169      *
170      * @see #invokeExactConstructor(java.lang.Class, java.lang.Object[], java.lang.Class[])
171      */
172     public static Object invokeExactConstructor(Class klass, Object arg)
173         throws
174             NoSuchMethodException,
175             IllegalAccessException,
176             InvocationTargetException,
177             InstantiationException {
178 
179         Object[] args = { arg };
180         return invokeExactConstructor(klass, args);
181 
182     }
183 
184     /**
185      * <p>Returns new instance of <code>klazz</code> created using the actual arguments <code>args</code>.
186      * The formal parameter types are inferred from the actual values of <code>args</code>.
187      * See {@link #invokeExactConstructor(Class, Object[], Class[])} for more details.</p>
188      *
189      * <p>The signatures should match exactly.</p>
190      *
191      * @param klass the class to be constructed.
192      * @param args actual argument array
193      * @return new instance of <code>klazz</code>
194      *
195      * @see #invokeExactConstructor(java.lang.Class, java.lang.Object[], java.lang.Class[])
196      */
197     public static Object invokeExactConstructor(Class klass, Object[] args)
198         throws
199             NoSuchMethodException,
200             IllegalAccessException,
201             InvocationTargetException,
202             InstantiationException {
203         if (null == args) {
204             args = emptyObjectArray;
205         }
206         int arguments = args.length;
207         Class parameterTypes[] = new Class[arguments];
208         for (int i = 0; i < arguments; i++) {
209             parameterTypes[i] = args[i].getClass();
210         }
211         return invokeExactConstructor(klass, args, parameterTypes);
212 
213     }
214 
215     /**
216      * <p>Returns new instance of <code>klazz</code> created using constructor
217      * with signature <code>parameterTypes</code> and actual arguments
218      * <code>args</code>.</p>
219      *
220      * <p>The signatures should match exactly.</p>
221      *
222      * @param klass the class to be constructed.
223      * @param args actual argument array
224      * @param parameterTypes parameter types array
225      * @return new instance of <code>klazz</code>
226      *
227      * @throws NoSuchMethodException if matching constructor cannot be found
228      * @throws IllegalAccessException thrown on the constructor's invocation
229      * @throws InvocationTargetException thrown on the constructor's invocation
230      * @throws InstantiationException thrown on the constructor's invocation
231      * @see Constructor#newInstance
232      */
233     public static Object invokeExactConstructor(
234         Class klass,
235         Object[] args,
236         Class[] parameterTypes)
237         throws
238             NoSuchMethodException,
239             IllegalAccessException,
240             InvocationTargetException,
241             InstantiationException {
242 
243         if (args == null) {
244             args = emptyObjectArray;
245         }
246 
247         if (parameterTypes == null) {
248             parameterTypes = emptyClassArray;
249         }
250 
251         Constructor ctor = getAccessibleConstructor(klass, parameterTypes);
252         if (null == ctor) {
253             throw new NoSuchMethodException(
254                 "No such accessible constructor on object: " + klass.getName());
255         }
256         return ctor.newInstance(args);
257 
258     }
259 
260     /**
261      * Returns a constructor with single argument.
262      * @param klass the class to be constructed
263      * @return null if matching accessible constructor can not be found.
264      * @see Class#getConstructor
265      * @see #getAccessibleConstructor(java.lang.reflect.Constructor)
266      */
267     public static Constructor getAccessibleConstructor(
268         Class klass,
269         Class parameterType) {
270 
271         Class[] parameterTypes = { parameterType };
272         return getAccessibleConstructor(klass, parameterTypes);
273 
274     }
275 
276     /**
277      * Returns a constructor given a class and signature.
278      * @param klass the class to be constructed
279      * @param parameterTypes the parameter array
280      * @return null if matching accessible constructor can not be found
281      * @see Class#getConstructor
282      * @see #getAccessibleConstructor(java.lang.reflect.Constructor)
283      */
284     public static Constructor getAccessibleConstructor(
285         Class klass,
286         Class[] parameterTypes) {
287 
288         try {
289             return getAccessibleConstructor(
290                 klass.getConstructor(parameterTypes));
291         } catch (NoSuchMethodException e) {
292             return (null);
293         }
294 
295     }
296 
297     /**
298      * Returns accessible version of the given constructor.
299      * @param ctor prototype constructor object.
300      * @return <code>null</code> if accessible constructor can not be found.
301      * @see java.lang.SecurityManager
302      */
303     public static Constructor getAccessibleConstructor(Constructor ctor) {
304 
305         // Make sure we have a method to check
306         if (ctor == null) {
307             return (null);
308         }
309 
310         // If the requested method is not public we cannot call it
311         if (!Modifier.isPublic(ctor.getModifiers())) {
312             return (null);
313         }
314 
315         // If the declaring class is public, we are done
316         Class clazz = ctor.getDeclaringClass();
317         if (Modifier.isPublic(clazz.getModifiers())) {
318             return (ctor);
319         }
320 
321         // what else can we do?
322         return null;
323 
324     }
325 
326     // -------------------------------------------------------- Private Methods
327     /**
328      * <p>Find an accessible constructor with compatible parameters.
329      * Compatible parameters mean that every method parameter is assignable from
330      * the given parameters. In other words, it finds constructor that will take
331      * the parameters given.</p>
332      *
333      * <p>First it checks if there is constructor matching the exact signature.
334      * If no such, all the constructors of the class are tested if their signatures
335      * are assignment compatible with the parameter types.
336      * The first matching constructor is returned.</p>
337      *
338      * @param clazz find constructor for this class
339      * @param parameterTypes find method with compatible parameters
340      * @return a valid Constructor object. If there's no matching constructor, returns <code>null</code>.
341      */
342     private static Constructor getMatchingAccessibleConstructor(
343         Class clazz,
344         Class[] parameterTypes) {
345         // see if we can find the method directly
346         // most of the time this works and it's much faster
347         try {
348             Constructor ctor = clazz.getConstructor(parameterTypes);
349             try {
350                 //
351                 // XXX Default access superclass workaround
352                 //
353                 // When a public class has a default access superclass
354                 // with public methods, these methods are accessible.
355                 // Calling them from compiled code works fine.
356                 //
357                 // Unfortunately, using reflection to invoke these methods
358                 // seems to (wrongly) to prevent access even when the method
359                 // modifer is public.
360                 //
361                 // The following workaround solves the problem but will only
362                 // work from sufficiently privilages code. 
363                 //
364                 // Better workarounds would be greatfully accepted.
365                 //
366                 ctor.setAccessible(true);
367             } catch (SecurityException se) {}
368             return ctor;
369 
370         } catch (NoSuchMethodException e) { /* SWALLOW */
371         }
372 
373         // search through all methods 
374         int paramSize = parameterTypes.length;
375         Constructor[] ctors = clazz.getConstructors();
376         for (int i = 0, size = ctors.length; i < size; i++) {
377             // compare parameters
378             Class[] ctorParams = ctors[i].getParameterTypes();
379             int ctorParamSize = ctorParams.length;
380             if (ctorParamSize == paramSize) {
381                 boolean match = true;
382                 for (int n = 0; n < ctorParamSize; n++) {
383                     if (!MethodUtils
384                         .isAssignmentCompatible(
385                             ctorParams[n],
386                             parameterTypes[n])) {
387                         match = false;
388                         break;
389                     }
390                 }
391 
392                 if (match) {
393                     // get accessible version of method
394                     Constructor ctor = getAccessibleConstructor(ctors[i]);
395                     if (ctor != null) {
396                         try {
397                             ctor.setAccessible(true);
398                         } catch (SecurityException se) {}
399                         return ctor;
400                     }
401                 }
402             }
403         }
404 
405         return null;
406     }
407 
408 }