001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018 package org.apache.commons.beanutils;
019
020 import java.lang.reflect.Constructor;
021 import java.lang.reflect.InvocationTargetException;
022 import java.lang.reflect.Modifier;
023
024 /**
025 * <p> Utility reflection methods focussed on constructors, modelled after {@link MethodUtils}. </p>
026 *
027 * <h3>Known Limitations</h3>
028 * <h4>Accessing Public Constructors In A Default Access Superclass</h4>
029 * <p>There is an issue when invoking public constructors contained in a default access superclass.
030 * Reflection locates these constructors fine and correctly assigns them as public.
031 * However, an <code>IllegalAccessException</code> is thrown if the constructors is invoked.</p>
032 *
033 * <p><code>ConstructorUtils</code> contains a workaround for this situation.
034 * It will attempt to call <code>setAccessible</code> on this constructor.
035 * If this call succeeds, then the method can be invoked as normal.
036 * This call will only succeed when the application has sufficient security privilages.
037 * If this call fails then a warning will be logged and the method may fail.</p>
038 *
039 * @author Craig R. McClanahan
040 * @author Ralph Schaer
041 * @author Chris Audley
042 * @author Rey Francois
043 * @author Gregor Rayman
044 * @author Jan Sorensen
045 * @author Robert Burrell Donkin
046 * @author Rodney Waldhoff
047 * @version $Revision: 555824 $ $Date: 2007-07-13 01:27:15 +0100 (Fri, 13 Jul 2007) $
048 */
049 public class ConstructorUtils {
050
051 // --------------------------------------------------------- Private Members
052 /** An empty class array */
053 private static final Class[] EMPTY_CLASS_PARAMETERS = new Class[0];
054 /** An empty object array */
055 private static final Object[] EMPTY_OBJECT_ARRAY = new Object[0];
056
057 // --------------------------------------------------------- Public Methods
058
059 /**
060 * <p>Convenience method returning new instance of <code>klazz</code> using a single argument constructor.
061 * The formal parameter type is inferred from the actual values of <code>arg</code>.
062 * See {@link #invokeExactConstructor(Class, Object[], Class[])} for more details.</p>
063 *
064 * <p>The signatures should be assignment compatible.</p>
065 *
066 * @param klass the class to be constructed.
067 * @param arg the actual argument
068 * @return new instance of <code>klazz</code>
069 *
070 * @throws NoSuchMethodException If the constructor cannot be found
071 * @throws IllegalAccessException If an error occurs accessing the constructor
072 * @throws InvocationTargetException If an error occurs invoking the constructor
073 * @throws InstantiationException If an error occurs instantiating the class
074 *
075 * @see #invokeConstructor(java.lang.Class, java.lang.Object[], java.lang.Class[])
076 */
077 public static Object invokeConstructor(Class klass, Object arg)
078 throws
079 NoSuchMethodException,
080 IllegalAccessException,
081 InvocationTargetException,
082 InstantiationException {
083
084 Object[] args = { arg };
085 return invokeConstructor(klass, args);
086
087 }
088
089 /**
090 * <p>Returns new instance of <code>klazz</code> created using the actual arguments <code>args</code>.
091 * The formal parameter types are inferred from the actual values of <code>args</code>.
092 * See {@link #invokeExactConstructor(Class, Object[], Class[])} for more details.</p>
093 *
094 * <p>The signatures should be assignment compatible.</p>
095 *
096 * @param klass the class to be constructed.
097 * @param args actual argument array
098 * @return new instance of <code>klazz</code>
099 *
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 }