View Javadoc

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