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 NoSuchMethodException, IllegalAccessException,
079: InvocationTargetException, InstantiationException {
080:
081: Object[] args = { arg };
082: return invokeConstructor(klass, args);
083:
084: }
085:
086: /**
087: * <p>Returns new instance of <code>klazz</code> created using the actual arguments <code>args</code>.
088: * The formal parameter types are inferred from the actual values of <code>args</code>.
089: * See {@link #invokeExactConstructor(Class, Object[], Class[])} for more details.</p>
090: *
091: * <p>The signatures should be assignment compatible.</p>
092: *
093: * @param klass the class to be constructed.
094: * @param args actual argument array
095: * @return new instance of <code>klazz</code>
096: *
097: * @throws NoSuchMethodException If the constructor cannot be found
098: * @throws IllegalAccessException If an error occurs accessing the constructor
099: * @throws InvocationTargetException If an error occurs invoking the constructor
100: * @throws InstantiationException If an error occurs instantiating the class
101: *
102: * @see #invokeConstructor(java.lang.Class, java.lang.Object[], java.lang.Class[])
103: */
104: public static Object invokeConstructor(Class klass, Object[] args)
105: throws NoSuchMethodException, IllegalAccessException,
106: InvocationTargetException, InstantiationException {
107:
108: if (null == args) {
109: args = EMPTY_OBJECT_ARRAY;
110: }
111: int arguments = args.length;
112: Class parameterTypes[] = new Class[arguments];
113: for (int i = 0; i < arguments; i++) {
114: parameterTypes[i] = args[i].getClass();
115: }
116: return invokeConstructor(klass, args, parameterTypes);
117:
118: }
119:
120: /**
121: * <p>Returns new instance of <code>klazz</code> created using constructor
122: * with signature <code>parameterTypes</code> and actual arguments <code>args</code>.</p>
123: *
124: * <p>The signatures should be assignment compatible.</p>
125: *
126: * @param klass the class to be constructed.
127: * @param args actual argument array
128: * @param parameterTypes parameter types array
129: * @return new instance of <code>klazz</code>
130: *
131: * @throws NoSuchMethodException if matching constructor cannot be found
132: * @throws IllegalAccessException thrown on the constructor's invocation
133: * @throws InvocationTargetException thrown on the constructor's invocation
134: * @throws InstantiationException thrown on the constructor's invocation
135: * @see Constructor#newInstance
136: */
137: public static Object invokeConstructor(Class klass, Object[] args,
138: Class[] parameterTypes) throws NoSuchMethodException,
139: IllegalAccessException, InvocationTargetException,
140: InstantiationException {
141:
142: if (parameterTypes == null) {
143: parameterTypes = EMPTY_CLASS_PARAMETERS;
144: }
145: if (args == null) {
146: args = EMPTY_OBJECT_ARRAY;
147: }
148:
149: Constructor ctor = getMatchingAccessibleConstructor(klass,
150: parameterTypes);
151: if (null == ctor) {
152: throw new NoSuchMethodException(
153: "No such accessible constructor on object: "
154: + klass.getName());
155: }
156: return ctor.newInstance(args);
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: * @throws NoSuchMethodException If the constructor cannot be found
171: * @throws IllegalAccessException If an error occurs accessing the constructor
172: * @throws InvocationTargetException If an error occurs invoking the constructor
173: * @throws InstantiationException If an error occurs instantiating the class
174: *
175: * @see #invokeExactConstructor(java.lang.Class, java.lang.Object[], java.lang.Class[])
176: */
177: public static Object invokeExactConstructor(Class klass, Object arg)
178: throws NoSuchMethodException, IllegalAccessException,
179: InvocationTargetException, InstantiationException {
180:
181: Object[] args = { arg };
182: return invokeExactConstructor(klass, args);
183:
184: }
185:
186: /**
187: * <p>Returns new instance of <code>klazz</code> created using the actual arguments <code>args</code>.
188: * The formal parameter types are inferred from the actual values of <code>args</code>.
189: * See {@link #invokeExactConstructor(Class, Object[], Class[])} for more details.</p>
190: *
191: * <p>The signatures should match exactly.</p>
192: *
193: * @param klass the class to be constructed.
194: * @param args actual argument array
195: * @return new instance of <code>klazz</code>
196: *
197: * @throws NoSuchMethodException If the constructor cannot be found
198: * @throws IllegalAccessException If an error occurs accessing the constructor
199: * @throws InvocationTargetException If an error occurs invoking the constructor
200: * @throws InstantiationException If an error occurs instantiating the class
201: *
202: * @see #invokeExactConstructor(java.lang.Class, java.lang.Object[], java.lang.Class[])
203: */
204: public static Object invokeExactConstructor(Class klass,
205: Object[] args) throws NoSuchMethodException,
206: IllegalAccessException, InvocationTargetException,
207: InstantiationException {
208: if (null == args) {
209: args = EMPTY_OBJECT_ARRAY;
210: }
211: int arguments = args.length;
212: Class parameterTypes[] = new Class[arguments];
213: for (int i = 0; i < arguments; i++) {
214: parameterTypes[i] = args[i].getClass();
215: }
216: return invokeExactConstructor(klass, args, parameterTypes);
217:
218: }
219:
220: /**
221: * <p>Returns new instance of <code>klazz</code> created using constructor
222: * with signature <code>parameterTypes</code> and actual arguments
223: * <code>args</code>.</p>
224: *
225: * <p>The signatures should match exactly.</p>
226: *
227: * @param klass the class to be constructed.
228: * @param args actual argument array
229: * @param parameterTypes parameter types array
230: * @return new instance of <code>klazz</code>
231: *
232: * @throws NoSuchMethodException if matching constructor cannot be found
233: * @throws IllegalAccessException thrown on the constructor's invocation
234: * @throws InvocationTargetException thrown on the constructor's invocation
235: * @throws InstantiationException thrown on the constructor's invocation
236: * @see Constructor#newInstance
237: */
238: public static Object invokeExactConstructor(Class klass,
239: Object[] args, Class[] parameterTypes)
240: throws NoSuchMethodException, IllegalAccessException,
241: InvocationTargetException, InstantiationException {
242:
243: if (args == null) {
244: args = EMPTY_OBJECT_ARRAY;
245: }
246:
247: if (parameterTypes == null) {
248: parameterTypes = EMPTY_CLASS_PARAMETERS;
249: }
250:
251: Constructor ctor = getAccessibleConstructor(klass,
252: parameterTypes);
253: if (null == ctor) {
254: throw new NoSuchMethodException(
255: "No such accessible constructor on object: "
256: + klass.getName());
257: }
258: return ctor.newInstance(args);
259:
260: }
261:
262: /**
263: * Returns a constructor with single argument.
264: * @param klass the class to be constructed
265: * @param parameterType The constructor parameter type
266: * @return null if matching accessible constructor can not be found.
267: * @see Class#getConstructor
268: * @see #getAccessibleConstructor(java.lang.reflect.Constructor)
269: */
270: public static Constructor getAccessibleConstructor(Class klass,
271: Class parameterType) {
272:
273: Class[] parameterTypes = { parameterType };
274: return getAccessibleConstructor(klass, parameterTypes);
275:
276: }
277:
278: /**
279: * Returns a constructor given a class and signature.
280: * @param klass the class to be constructed
281: * @param parameterTypes the parameter array
282: * @return null if matching accessible constructor can not be found
283: * @see Class#getConstructor
284: * @see #getAccessibleConstructor(java.lang.reflect.Constructor)
285: */
286: public static Constructor getAccessibleConstructor(Class klass,
287: Class[] parameterTypes) {
288:
289: try {
290: return getAccessibleConstructor(klass
291: .getConstructor(parameterTypes));
292: } catch (NoSuchMethodException e) {
293: return (null);
294: }
295:
296: }
297:
298: /**
299: * Returns accessible version of the given constructor.
300: * @param ctor prototype constructor object.
301: * @return <code>null</code> if accessible constructor can not be found.
302: * @see java.lang.SecurityManager
303: */
304: public static Constructor getAccessibleConstructor(Constructor ctor) {
305:
306: // Make sure we have a method to check
307: if (ctor == null) {
308: return (null);
309: }
310:
311: // If the requested method is not public we cannot call it
312: if (!Modifier.isPublic(ctor.getModifiers())) {
313: return (null);
314: }
315:
316: // If the declaring class is public, we are done
317: Class clazz = ctor.getDeclaringClass();
318: if (Modifier.isPublic(clazz.getModifiers())) {
319: return (ctor);
320: }
321:
322: // what else can we do?
323: return null;
324:
325: }
326:
327: // -------------------------------------------------------- Private Methods
328: /**
329: * <p>Find an accessible constructor with compatible parameters.
330: * Compatible parameters mean that every method parameter is assignable from
331: * the given parameters. In other words, it finds constructor that will take
332: * the parameters given.</p>
333: *
334: * <p>First it checks if there is constructor matching the exact signature.
335: * If no such, all the constructors of the class are tested if their signatures
336: * are assignment compatible with the parameter types.
337: * The first matching constructor is returned.</p>
338: *
339: * @param clazz find constructor for this class
340: * @param parameterTypes find method with compatible parameters
341: * @return a valid Constructor object. If there's no matching constructor, returns <code>null</code>.
342: */
343: private static Constructor getMatchingAccessibleConstructor(
344: Class clazz, 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: /* SWALLOW, if workaround fails don't fret. */
369: }
370: return ctor;
371:
372: } catch (NoSuchMethodException e) { /* SWALLOW */
373: }
374:
375: // search through all methods
376: int paramSize = parameterTypes.length;
377: Constructor[] ctors = clazz.getConstructors();
378: for (int i = 0, size = ctors.length; i < size; i++) {
379: // compare parameters
380: Class[] ctorParams = ctors[i].getParameterTypes();
381: int ctorParamSize = ctorParams.length;
382: if (ctorParamSize == paramSize) {
383: boolean match = true;
384: for (int n = 0; n < ctorParamSize; n++) {
385: if (!MethodUtils.isAssignmentCompatible(
386: ctorParams[n], 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: /* Swallow SecurityException
400: * TODO: Why?
401: */
402: }
403: return ctor;
404: }
405: }
406: }
407: }
408:
409: return null;
410: }
411:
412: }
|