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
018package org.apache.commons.beanutils;
019
020import java.lang.reflect.Constructor;
021import java.lang.reflect.InvocationTargetException;
022import 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 */
049public 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}