/*==========================================================================*\
| $Id: ReflectionSupport.java,v 1.8 2010/05/27 14:24:21 stedwar2 Exp $
|*-------------------------------------------------------------------------*|
| Copyright (C) 2007 Virginia Tech
|
| This file is part of Web-CAT.
|
| Web-CAT is free software; you can redistribute it and/or modify
| it under the terms of the GNU General Public License as published by
| the Free Software Foundation; either version 2 of the License, or
| (at your option) any later version.
|
| Web-CAT is distributed in the hope that it will be useful,
| but WITHOUT ANY WARRANTY; without even the implied warranty of
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
| GNU General Public License for more details.
|
| You should have received a copy of the GNU General Public License
| along with Web-CAT; if not, write to the Free Software
| Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
| Project manager: Stephen Edwards <edwards@cs.vt.edu>
| Virginia Tech CS Dept, 660 McBryde Hall (0106), Blacksburg, VA 24061 USA
\*==========================================================================*/
package net.sf.webcat;
import java.lang.reflect.*;
import static junit.framework.Assert.*;
//-------------------------------------------------------------------------
/**
* This class provides static helper methods to use reflection to check
* for and invoke methods on objects. It is intended for use in test
* cases, and makes it easier to write test cases that compile successfully
* but fail at run-time if the class under test fails to provide
* required methods (or provides them with the wrong signature). For
* Web-CAT users, this makes it possible to get partial scores, even when
* some methods are not even provided in the class under test.
* <p>
* Consider a situation where you are writing a test case that invokes the
* <code>doIt()</code> method on a given object. You might do it like
* this:
* </p>
* <pre>
* MyType result = receiver.doIt(param1, param2); // returns a value
* // ...
* receiver.doSomethingElse(); // a void method with no parameters
* </pre>
* <p>The first line passes two parameters to <code>doIt()</code> and stores
* the return value in a local variable called <code>result</code>. All
* this would be fine if the class under test actually provides a method
* called <code>doIt()</code> with the appropriate signature and return
* type. Otherwise, you would get a compile-time error. But on Web-CAT
* a compilation error in a test suite would give a resulting score of
* zero--no successful compilation, no partial credit. The story is similar
* with the second line, which simply calls a void method on the class
* under test.
* </p>
* <p>What if, instead, you wanted to write test cases that <em>checked
* for</em> specific methods, and either succeeded or failed depending on
* whether the method was present? You can use the methods in this
* class to write such test cases. So instead of writing the call
* above, you could do this:</p>
* <pre>
* // At the top of your test class
* import static net.sf.webcat.ReflectionSupport.*;
*
* // ...
*
* // For a method that returns a result:
* MyType result = invoke(receiver, MyType.class, "doIt", param1, param2);
*
* // Or for a void method:
* invoke(receiver, "doSomethingElse");
* </pre>
* <p>
* The syntax is simple and straight forward. The overloaded invoke
* method can be used on functions (methods that return a value) or
* procedures (void methods that return nothing). For methods that
* return a value, you specify the type of the return value as the
* second parameter. If you omit the return type, then it is assumed to
* be "void". You specify the name of the method as a string, and then
* a variable length set of arguments.
* </p>
* <p>
* If you are calling a method that is returning a primitive type, be
* sure to use the corresponding wrapper class as the expected return
* value:
* </p>
* <pre>
* // Instead of boolean answer = receiver.equals(anotherObject);
* Boolean answer = invoke(receiver, Boolean.class, "equals", anotherObject);
* // Or in an assert, using auto-unboxing:
* assertTrue(invoke(receiver, Boolean.class, "equals", anotherObject));
* </pre>
* <p>
* Any errors that occur during reflection, such as failing to find the
* required method, failing to find a method with the required signature,
* finding a method that is not public, etc., are converted into
* appropriate test case errors with meaningful diagnostic hints for the
* student. Any exceptions are wrapped in a RuntimeException and need not
* be explicitly caught by the caller (they will turn into test case
* failures as well).
* </p>
*
* @author stedwar2
* @version $Id: ReflectionSupport.java,v 1.8 2010/05/27 14:24:21 stedwar2 Exp $
*/
public class ReflectionSupport
{
//~ Constructor ...........................................................
// ----------------------------------------------------------
/**
* This class only provides static methods, to the constructor is
* private.
*/
private ReflectionSupport()
{
// Nothing to do
}
//~ Methods ...............................................................
// ----------------------------------------------------------
/**
* Returns the name of the given class without any package prefix.
* If the argument is an array type, square brackets are added to
* the name as appropriate. This method isuseful in generating
* diagnostic messages or feedback.
* @param aClass The class to generate a name for
* @return The class' name, without the package part, e.g., "String"
* instead of "java.lang.String"
*/
public static String simpleClassName(Class<?> aClass)
{
if (aClass == null) return "null";
String result = aClass.getName();
// If it is an array, add appropriate number of brackets
try
{
Class<?> cl = aClass;
while (cl.isArray())
{
result += "[]";
cl = cl.getComponentType();
}
}
catch (Throwable e)
{
// Swallow it and stick with the bare class name
}
int pos = result.lastIndexOf('.');
if (pos >= 0)
{
result = result.substring(pos + 1);
}
return result;
}
// ----------------------------------------------------------
/**
* Returns the name of the given class without any package prefix.
* If the argument is an array type, square brackets are added to
* the name as appropriate. This method isuseful in generating
* diagnostic messages or feedback.
* @param aClass The class to generate a name for
* @return The class' name, without the package part, e.g., "String"
* instead of "java.lang.String"
*/
public static String simpleClassNameUsingPrimitives(Class<?> aClass)
{
if (aClass == Boolean.class)
{
aClass = Boolean.TYPE;
}
else if (aClass == Byte.class)
{
aClass = Byte.TYPE;
}
else if (aClass == Character.class)
{
aClass = Character.TYPE;
}
else if (aClass == Short.class)
{
aClass = Short.TYPE;
}
else if (aClass == Integer.class)
{
aClass = Integer.TYPE;
}
else if (aClass == Long.class)
{
aClass = Long.TYPE;
}
else if (aClass == Float.class)
{
aClass = Float.TYPE;
}
else if (aClass == Double.class)
{
aClass = Double.TYPE;
}
return simpleClassName(aClass);
}
// ----------------------------------------------------------
/**
* Constructs a printable version of a method's name, given
* the method name and its parameter type(s), if any.
* Useful in generating diagnostic messages or feedback.
* @param name The method name
* @param params The method's parameter type(s), in order
* @return A printable version of the method name, like
* "myMethod()" or "yourMethod(String, int)"
*/
public static String simpleMethodName(String name, Class<?> ... params)
{
return name + simpleArgumentList(params);
}
// ----------------------------------------------------------
/**
* Constructs a printable version of a method's argument list, including
* the parentheses, given the method's parameter type(s), if any.
* @param params The method's parameter type(s), in order
* @return A printable version of the argument list built using
* {@link #simpleClassName(Class)}, like "(String, int)"
*/
public static String simpleArgumentList(Class<?> ... params)
{
String result = "(";
boolean needsComma = false;
for (Class<?> c : params)
{
if (needsComma)
{
result += ", ";
}
if (c == null)
{
result += "null";
}
else
{
result += simpleClassName(c);
}
needsComma = true;
}
result += ")";
return result;
}
// ----------------------------------------------------------
/**
* Constructs a printable version of a method's name. Unlike
* {@link Method#toString()}, this one uses {@link #simpleClassName(Class)}
* so package info is eliminated from the types in the resulting
* string. It also omits exception information, unlike Method.toString().
* @param method The method to print
* @return A printable version of the method name, like
* "public void MyClass.myMethod()" or
* "public String YourClass.yourMethod(String, int)"
*/
public static String simpleMethodName(Method method)
{
StringBuffer sb = new StringBuffer();
int mod = method.getModifiers();
if (mod != 0)
{
sb.append(Modifier.toString(mod) + " ");
}
sb.append(simpleClassName(method.getReturnType()) + " ");
sb.append(method.getName());
sb.append(simpleArgumentList(method.getParameterTypes()));
sb.append(" in class ");
sb.append(simpleClassName(method.getDeclaringClass()));
return sb.toString();
}
// ----------------------------------------------------------
/**
* Determine whether an actual argument type matches a formal argument
* type. This uses {@link Class#isAssignableFrom(Class)}, but gives
* the correct results for primitive types vs. wrapper types.
* @param actual The type of the actual parameter
* @param formal The type of the formal parameter
* @return True if the actual value can be passed into a parameter
* declared using the formal type
*/
public static boolean actualMatchesFormal(Class<?> actual, Class<?> formal)
{
boolean result = formal.isAssignableFrom(actual);
if (!result)
{
result = canAutoBoxFromActualToFormal(actual, formal);
}
return result;
}
// ----------------------------------------------------------
public static boolean canAutoBoxFromActualToFormal(
Class<?> actual, Class<?> formal)
{
return
( ( formal.equals(byte.class)
|| formal.equals(short.class)
|| formal.equals(int.class)
|| formal.equals(long.class)
|| formal.equals(float.class)
|| formal.equals(double.class) )
&& Number.class.isAssignableFrom(actual) )
|| ( formal.equals(boolean.class)
&& actual.equals(Boolean.class) )
|| ( formal.equals(char.class)
&& actual.equals(Character.class) );
}
// ----------------------------------------------------------
/**
* Look up a method by name and parameter profile, turning any
* errors into test case failures with appropriate hint messages.
* Only looks up methods that are declared in the specified class,
* not inherited methods. Assumes the intended method should be
* public, and fails with an appropriate hint if it is not.
* @param c The type of the receiver
* @param name The method name
* @param params The method's parameter profile
* @return The corresponding Method object
*/
public static Method getMethod(
Class<?> c, String name, Class<?> ... params)
{
Method m = null;
try
{
m = c.getDeclaredMethod(name, params);
}
catch (NoSuchMethodException e)
{
String message = c + " is missing method "
+ simpleMethodName(name, params);
fail(message);
}
catch (SecurityException e)
{
String message = "method " + simpleMethodName(name, params)
+ " in " + c + " cannot be accessed (should be public)";
fail(message);
}
if (m != null && !Modifier.isPublic(m.getModifiers()))
{
fail(simpleMethodName(m) + " should be public");
}
return m;
}
// ----------------------------------------------------------
/**
* Look up a method by name and parameter profile, finding the
* method that will accept the given list of parameters (not requiring
* an exact match on parameter types). It turns any
* errors into test case failures with appropriate hint messages.
* Only looks up methods that are declared in the specified class,
* not inherited methods. Assumes the intended method should be
* public, and fails with an appropriate hint if it is not.
* Note that this method <b>does not handle variable argument lists</b>
* in the target method for which it is searching.
* @param c The type of the receiver
* @param name The method name
* @param params The method's parameter profile
* @return The corresponding Method object
*/
public static Method getMatchingMethod(
Class<?> c, String name, Class<?> ... params)
{
Method result = null;
Method methodWithSameName = null;
Method methodWithSameParamCount = null;
if (params == null) { params = new Class[0]; }
for (Method m : c.getMethods())
{
if (m.getName().equals(name))
{
methodWithSameName = m;
Class<?>[] paramTypes = m.getParameterTypes();
if (params.length == paramTypes.length)
{
methodWithSameParamCount = m;
result = m; // maybe ... we'll clear it if wrong
for (int i = 0; i < params.length; i++)
{
if (params[i] != null)
{
// If the actual is non-null, check to see if
// it can be assigned to the formal correctly.
if (!actualMatchesFormal(params[i], paramTypes[i]))
{
result = null;
break;
}
}
else if (paramTypes[i].isPrimitive())
{
// If actual is null, then the formal can't
// be a primitive
result = null;
break;
}
}
if (result != null)
{
// If we found a match that can accept all the
// parameters ...
break;
}
}
}
}
if (result == null)
{
String message = null;
if (methodWithSameParamCount != null)
{
message = simpleMethodName(methodWithSameParamCount)
+ " cannot be called with argument"
+ ((params.length == 1) ? "" : "s")
+ " of type "
+ simpleArgumentList(params)
+ ": incorrect parameter type(s)";
}
else if (methodWithSameName != null)
{
message = simpleMethodName(methodWithSameName);
if (params.length == 0)
{
message += " cannot be called with no arguments";
}
else
{
message += " cannot be called with argument"
+ ((params.length == 1) ? "" : "s")
+ " of type "
+ simpleArgumentList(params);
}
message += ": incorrect number of parameters";
}
else
{
message = "" + c + " is missing public method "
+ simpleMethodName(name, params);
}
fail(message);
}
else if (!Modifier.isPublic(result.getModifiers()))
{
fail(simpleMethodName(result) + " should be public");
}
return result;
}
// ----------------------------------------------------------
/**
* Dynamically look up and invoke a method on a target object, with
* appropriate hints if any failures happen along the way.
* @param receiver The object to invoke the method on
* @param returnType The expected type of the method's return value.
* Use null (or <code>void.class</code>) if the method that is
* looked up is a void method.
* @param methodName The name of the method to invoke
* @param params The parameters to pass to the method
* @param <T> The generic parameter T is deduced from the returnType
* @return The results from invoking the given method
*/
@SuppressWarnings("unchecked")
public static <T> T invoke(
Object receiver,
Class<T> returnType,
String methodName,
Object ... params)
{
Object result = null;
Class targetClass = receiver.getClass();
Class[] paramProfile = null;
if (params != null)
{
paramProfile = new Class[params.length];
for (int i = 0; i < params.length; i++)
{
if ( params[i] == null)
{
// A null indicates we'll try to pass null as an
// actual in the getMatchingMethod() search
paramProfile[i] = null;
}
else
{
paramProfile[i] = params[i].getClass();
}
}
}
Method m = getMatchingMethod(targetClass, methodName, paramProfile);
if (returnType == null || returnType == void.class)
{
Class<?> declaredReturnType = m.getReturnType();
assertTrue("method " + simpleMethodName(m)
+ " should be a void method",
declaredReturnType == void.class ||
declaredReturnType == null);
}
else
{
Class<?> declaredReturnType = m.getReturnType();
assertTrue("method " + simpleMethodName(m)
+ " should be declared with a return type of "
+ simpleClassNameUsingPrimitives(returnType),
declaredReturnType != void.class &&
declaredReturnType != null &&
(actualMatchesFormal(declaredReturnType, returnType)
// Had to add this second part in for legacy compatibility,
// where tests written with Integer.class need to
// work, even though they should have been written
// with int.class
|| canAutoBoxFromActualToFormal(
returnType, declaredReturnType)));
}
result = invoke(receiver, m, params);
if (result != null)
{
if (returnType != null)
{
assertTrue("method " + simpleMethodName(m)
+ " should be a void method",
returnType != void.class);
assertTrue("method " + simpleMethodName(m)
+ " did not produce result of type "
+ simpleClassName(returnType),
actualMatchesFormal(result.getClass(), returnType));
}
else
{
fail("method " + simpleMethodName(m)
+ " should be a void method");
}
}
// The cast below is technically unsafe, according to the compiler,
// but will never be violated, due to the assertion above.
return (T)result;
}
// ----------------------------------------------------------
/**
* Dynamically look up and invoke a method on a target object, with
* appropriate hints if any failures happen along the way. This
* version is intended for calling "void" methods that have no
* return value.
* @param receiver The object to invoke the method on
* @param methodName The name of the method to invoke
* @param params The parameters to pass to the method
*/
public static void invoke(
Object receiver,
String methodName,
Object ... params)
{
invoke(receiver, void.class, methodName, params);
}
// ----------------------------------------------------------
/**
* Just like {@link Method#invoke(Object, Object...)}, but converts
* any thrown exceptions into RuntimeExceptions.
* @param receiver The object to invoke the method on
* @param method The method to invoke
* @param params The parameters to pass to the method
* @return The result from the method
*/
public static Object invoke(
Object receiver, Method method, Object ... params)
{
Object result = null;
try
{
result = method.invoke(receiver, params);
}
catch (InvocationTargetException e)
{
Throwable cause = e;
while (cause.getCause() != null)
{
cause = cause.getCause();
}
if (cause instanceof Error)
{
throw (Error)cause;
}
else if (cause instanceof RuntimeException)
{
throw (RuntimeException)cause;
}
else
{
throw new RuntimeException(cause);
}
}
catch (IllegalAccessException e)
{
// This should never happen, since getMethod() has already
// done the appropriate checks.
throw new RuntimeException(e);
}
return result;
}
// ----------------------------------------------------------
/**
* Just like {@link #invoke(Object, Class, String, Object...)}, but unwraps
* any InvocationTargetExceptions and throws the true cause. This
* version is provided when you want to write test cases where you
* are intending to check for Exceptions as expected results.
* @param receiver The object to invoke the method on
* @param returnType The expected type of the method's return value.
* Use null (or <code>void.class</code>) if the method that is
* looked up is a void method.
* @param methodName The name of the method to invoke
* @param params The parameters to pass to the method
* @param <T> The generic parameter T is deduced from the returnType
* @return The results from invoking the given method
* @throws Exception if the underlying method throws one
*/
@SuppressWarnings("unchecked")
public static <T> T invokeEx(
Object receiver,
Class<T> returnType,
String methodName,
Object ... params)
throws Exception
{
Object result = null;
Class targetClass = receiver.getClass();
Class[] paramProfile = null;
if (params != null)
{
paramProfile = new Class[params.length];
for (int i = 0; i < params.length; i++)
{
if ( params[i] == null)
{
// A null indicates we'll try to pass null as an
// actual in the getMatchingMethod() search
paramProfile[i] = null;
}
else
{
paramProfile[i] = params[i].getClass();
}
}
}
Method m = getMatchingMethod(targetClass, methodName, paramProfile);
if (returnType == null || returnType == void.class)
{
Class<?> declaredReturnType = m.getReturnType();
assertTrue("method " + simpleMethodName(m)
+ " should be a void method",
declaredReturnType == void.class ||
declaredReturnType == null);
}
else
{
Class<?> declaredReturnType = m.getReturnType();
assertTrue("method " + simpleMethodName(m)
+ " should be declared with a return type of "
+ simpleClassNameUsingPrimitives(returnType),
declaredReturnType != void.class &&
declaredReturnType != null &&
(actualMatchesFormal(declaredReturnType, returnType)
// Had to add this second part in for legacy compatibility,
// where tests written with Integer.class need to
// work, even though they should have been written
// with int.class
|| canAutoBoxFromActualToFormal(
returnType, declaredReturnType)));
}
result = invokeEx(receiver, m, params);
if (result != null)
{
if (returnType != null)
{
assertTrue("method " + simpleMethodName(m)
+ " should be a void method",
returnType != void.class);
assertTrue("method " + simpleMethodName(m)
+ " did not produce result of type "
+ simpleClassName(returnType),
returnType.isAssignableFrom(result.getClass()));
}
else
{
fail("method " + simpleMethodName(m)
+ " should be a void method");
}
}
// The cast below is technically unsafe, according to the compiler,
// but will never be violated, due to the assertion above.
return (T)result;
}
// ----------------------------------------------------------
/**
* Just like {@link #invoke(Object, String, Object...)}, but unwraps
* any InvocationTargetExceptions and throws the true cause. This
* version is provided when you want to write test cases where you
* are intending to check for Exceptions as expected results.
* @param receiver The object to invoke the method on
* @param methodName The name of the method to invoke
* @param params The parameters to pass to the method
* @throws Exception if the underlying method throws one
*/
public static void invokeEx(
Object receiver,
String methodName,
Object ... params)
throws Exception
{
invokeEx(receiver, void.class, methodName, params);
}
// ----------------------------------------------------------
/**
* Just like {@link Method#invoke(Object, Object...)}, but unwraps
* any InvocationTargetExceptions and throws the true cause. This
* version is provided when you want to write test cases where you
* are intending to check for Exceptions as expected results.
* @param receiver The object to invoke the method on
* @param method The method to invoke
* @param params The parameters to pass to the method
* @return The result from the method
* @throws Exception if the underlying method throws one
*/
public static Object invokeEx(
Object receiver, Method method, Object ... params)
throws Exception
{
Object result = null;
try
{
result = method.invoke(receiver, params);
}
catch (InvocationTargetException e)
{
Throwable cause = e;
Exception ex = null;
Error error = null;
if (cause instanceof Exception)
{
ex = (Exception)cause;
}
else if (cause instanceof Error)
{
error = (Error)cause;
}
while (cause.getCause() != null)
{
cause = cause.getCause();
if (cause instanceof Exception)
{
ex = (Exception)cause;
}
else if (cause instanceof Error)
{
error = (Error)cause;
}
}
if (error != null)
{
throw error;
}
else if (ex != null)
{
throw ex;
}
else
{
// the cause is a raw Throwable of some kind, rather than
// an Exception, so it needs to be wrapped anyway
throw new RuntimeException(cause);
}
}
return result;
}
// ----------------------------------------------------------
/**
* Look up a constructor by parameter profile, finding the
* constructor that will accept the given list of parameters (not requiring
* an exact match on parameter types). It turns any
* errors into test case failures with appropriate hint messages.
* Assumes the intended constructor should be public, and fails with an
* appropriate hint if it is not.
* Note that this method <b>does not handle variable argument lists</b>
* in the target constructor for which it is searching.
* @param c The type of object to create
* @param params The constructor's parameter profile
* @return The corresponding Constructor object
*/
public static Constructor<?> getMatchingConstructor(
Class<?> c, Class<?> ... params)
{
Constructor<?> result = null;
Constructor<?> ctorWithSameParamCount = null;
if (params == null) { params = new Class[0]; }
for (Constructor<?> m : c.getConstructors())
{
Class<?>[] paramTypes = m.getParameterTypes();
if (params.length == paramTypes.length)
{
ctorWithSameParamCount = m;
result = m; // maybe ... we'll clear it if wrong
for (int i = 0; i < params.length; i++)
{
if (params[i] != null)
{
// If the actual is non-null, check to see if
// it can be assigned to the formal correctly.
if (!actualMatchesFormal(params[i], paramTypes[i]))
{
result = null;
break;
}
}
else if (paramTypes[i].isPrimitive())
{
// If actual is null, then the formal can't
// be a primitive
result = null;
break;
}
}
if (result != null)
{
// If we found a match that can accept all the
// parameters ...
break;
}
}
}
if (result == null)
{
String message = null;
if (ctorWithSameParamCount != null)
{
message = "constructor cannot be called with argument"
+ ((params.length == 1) ? "" : "s")
+ " of type "
+ simpleArgumentList(params)
+ ": incorrect parameter type(s)";
}
else
{
message = "" + c + " is missing public constructor "
+ simpleMethodName(simpleClassName(c), params);
}
fail(message);
}
else if (!Modifier.isPublic(result.getModifiers()))
{
fail("constructor " + simpleMethodName(simpleClassName(c), params)
+ " should be public");
}
return result;
}
// ----------------------------------------------------------
/**
* Just like {@link Constructor#newInstance(Object...)}, but converts
* any thrown exceptions into RuntimeExceptions.
* @param constructor The constructor to invoke
* @param params The parameters to pass to the constructor
* @return The newly created object
*/
public static Object create(Constructor<?> constructor, Object ... params)
{
Object result = null;
try
{
result = constructor.newInstance(params);
}
catch (InvocationTargetException e)
{
Throwable cause = e;
while (cause.getCause() != null)
{
cause = cause.getCause();
}
throw new RuntimeException(cause);
}
catch (InstantiationException e)
{
Throwable cause = e;
while (cause.getCause() != null)
{
cause = cause.getCause();
}
throw new RuntimeException(cause);
}
catch (IllegalAccessException e)
{
// This should never happen, since getMethod() has already
// done the appropriate checks.
throw new RuntimeException(e);
}
return result;
}
// ----------------------------------------------------------
/**
* Dynamically look up and invoke a class constructor for the target
* class, with appropriate hints if any failures happen along the way.
* @param returnType The type of object to create.
* @param params The parameters to pass to the constructor
* @param <T> The generic parameter T is deduced from the returnType
* @return The newly created object
*/
@SuppressWarnings("unchecked")
public static <T> T create(
Class<T> returnType,
Object ... params)
{
Object result = null;
Class[] paramProfile = null;
if (params != null)
{
paramProfile = new Class[params.length];
for (int i = 0; i < params.length; i++)
{
if ( params[i] == null)
{
// A null indicates we'll try to pass null as an
// actual in the getMatchingMethod() search
paramProfile[i] = null;
}
else
{
paramProfile[i] = params[i].getClass();
}
}
}
Constructor c = getMatchingConstructor(returnType, paramProfile);
result = create(c, params);
if (result != null)
{
assertTrue("constructor "
+ simpleMethodName(simpleClassName(returnType), paramProfile)
+ " did not produce result of type "
+ simpleClassName(returnType),
returnType.isAssignableFrom(result.getClass()));
}
// The cast below is technically unsafe, according to the compiler,
// but will never be violated, due to the assertion above.
return (T)result;
}
// ----------------------------------------------------------
/**
* Dynamically look up and invoke a class constructor for the target
* class, with appropriate hints if any failures happen along the way.
* @param className The type of object to create
* @param params The parameters to pass to the constructor
* @return The newly created object
*/
public static Object create(String className, Object ... params)
{
return create(getClassForName(className), params);
}
// ----------------------------------------------------------
/**
* Just like {@link #create(Constructor, Object...)}, but unwraps
* any InvocationTargetExceptions and throws the true cause. This
* version is provided when you want to write test cases where you
* are intending to check for Exceptions as expected results.
* @param constructor The constructor to invoke
* @param params The parameters to pass to the constructor
* @return The newly created object
* @throws Exception if the underlying method throws one
*/
public static Object createEx(Constructor<?> constructor, Object ... params)
throws Exception
{
Object result = null;
try
{
result = constructor.newInstance(params);
}
catch (InvocationTargetException e)
{
Throwable cause = e;
Exception ex = null;
if (cause instanceof Exception)
{
ex = (Exception)cause;
}
while (cause.getCause() != null)
{
cause = cause.getCause();
if (cause instanceof Exception)
{
ex = (Exception)cause;
}
}
if (ex != null)
{
throw ex;
}
else
{
// the cause is a raw Throwable of some kind, rather than
// an Exception, so it needs to be wrapped anyway
throw new RuntimeException(cause);
}
}
return result;
}
// ----------------------------------------------------------
/**
* Just like {@link #create(Class, Object...)}, but unwraps
* any InvocationTargetExceptions and throws the true cause. This
* version is provided when you want to write test cases where you
* are intending to check for Exceptions as expected results.
* @param returnType The type of object to create.
* @param params The parameters to pass to the constructor
* @param <T> The generic parameter T is deduced from the returnType
* @return The newly created object
* @throws Exception if the underlying method throws one
*/
@SuppressWarnings("unchecked")
public static <T> T createEx(
Class<T> returnType,
Object ... params)
throws Exception
{
Object result = null;
Class[] paramProfile = null;
if (params != null)
{
paramProfile = new Class[params.length];
for (int i = 0; i < params.length; i++)
{
if ( params[i] == null)
{
// A null indicates we'll try to pass null as an
// actual in the getMatchingMethod() search
paramProfile[i] = null;
}
else
{
paramProfile[i] = params[i].getClass();
}
}
}
Constructor c = getMatchingConstructor(returnType, paramProfile);
result = createEx(c, params);
if (result != null)
{
assertTrue("constructor "
+ simpleMethodName(simpleClassName(returnType), paramProfile)
+ " did not produce result of type "
+ simpleClassName(returnType),
returnType.isAssignableFrom(result.getClass()));
}
// The cast below is technically unsafe, according to the compiler,
// but will never be violated, due to the assertion above.
return (T)result;
}
// ----------------------------------------------------------
/**
* Just like {@link #create(String, Object...)}, but unwraps
* any InvocationTargetExceptions and throws the true cause. This
* version is provided when you want to write test cases where you
* are intending to check for Exceptions as expected results.
* @param className The type of object to create
* @param params The parameters to pass to the constructor
* @return The newly created object
* @throws Exception if the underlying method throws one
*/
public static Object createEx(String className, Object ... params)
throws Exception
{
return createEx(getClassForName(className), params);
}
// ----------------------------------------------------------
/**
* Dynamically look up a class by name, with appropriate hints if the
* class cannot be found.
* @param className The type of object to create
* @return The corresponding Class object
*/
public static Class<?> getClassForName(String className)
{
try
{
// First, look in this class' class loader
return Class.forName(className);
}
catch (ClassNotFoundException e)
{
try
{
// Otherwise, try the executing thread's context class
// loader, in case it is different
return Thread.currentThread().getContextClassLoader()
.loadClass(className);
}
catch (ClassNotFoundException ee)
{
fail("cannot find class " + className);
// Just to make the compiler happy:
return null;
}
}
}
}