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    }