/*==========================================================================*\
| $Id: ReflectionSupport.java,v 1.8 2012/02/29 03:57:51 stedwar2 Exp $
|*-------------------------------------------------------------------------*|
| Copyright (C) 2007-2011 Virginia Tech
|
| This file is part of the Student-Library.
|
| The Student-Library is free software; you can redistribute it and/or
| modify it under the terms of the GNU Lesser General Public License as
| published by the Free Software Foundation; either version 3 of the
| License, or (at your option) any later version.
|
| The Student-Library 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 Lesser General Public License for more details.
|
| You should have received a copy of the GNU Lesser General Public License
| along with the Student-Library; if not, see <http://www.gnu.org/licenses/>.
\*==========================================================================*/
package student.testingsupport;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.security.AccessController;
import java.security.PrivilegedAction;
//-------------------------------------------------------------------------
/**
* 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 Stephen Edwards
* @author Last changed by $Author: stedwar2 $
* @version $Revision: 1.8 $, $Date: 2012/02/29 03:57:51 $
*/
public class ReflectionSupport
{
//~ Constructor ...........................................................
// ----------------------------------------------------------
/**
* This class only provides static methods, to the constructor is
* private.
*/
private ReflectionSupport()
{
// Nothing to do
}
//~ Public classes used inside this class .................................
// ----------------------------------------------------------
/**
* A custom error class that represents any error conditions that
* arise in the reflection-based methods provided by this class.
*/
public static class ReflectionError
extends AssertionError
{
private static final long serialVersionUID = 4456797064805957471L;
ReflectionError(String message)
{
super(message);
}
}
// ----------------------------------------------------------
/**
* An enumeration that represents a set of constants for specifying
* constraints on the visibility of a declaration.
*/
public static enum VisibilityConstraint
{
/** Declared with private visibility (only). */
DECLARED_PRIVATE(Modifier.PRIVATE),
/** Declared with package-level (default) visibility (only). */
DECLARED_PACKAGE(
Modifier.PRIVATE | Modifier.PROTECTED | Modifier.PUBLIC, true),
/** Declared with protected visibility (only). */
DECLARED_PROTECTED(Modifier.PROTECTED),
/** Declared with public visibility (only). */
DECLARED_PUBLIC(Modifier.PUBLIC),
/** Declared with protected or public visibility. */
AT_LEAST_PROTECTED(Modifier.PROTECTED | Modifier.PUBLIC),
/** Declared with package-level (default), protected, or public
* visibility.
*/
AT_LEAST_PACKAGE(Modifier.PRIVATE, true),
/** Declared with any visibility. */
ANY_VISIBILITY(0)
{
public boolean accepts(int modifiers) { return true; }
};
// ----------------------------------------------------------
private VisibilityConstraint(int mask)
{
this(mask, false);
}
// ----------------------------------------------------------
private VisibilityConstraint(int mask, boolean reverseMask)
{
this.mask = mask;
this.reverseMask = reverseMask;
}
// ----------------------------------------------------------
/**
* Determine if a given set of modifiers, expressed as an integer
* mask, meets this visibility constraint.
* @param modifiers The modifiers to check.
* @return True if the modifiers are consistent with this constraint.
*/
public boolean accepts(int modifiers)
{
boolean result = reverseMask;
if ((mask & modifiers) != 0)
{
result = !result;
}
return result;
}
// ----------------------------------------------------------
/**
* Determine if a given member meets this visibility constraint.
* @param member The member to check.
* @return True if the member's visibility is consistent with this
* constraint.
*/
public boolean accepts(java.lang.reflect.Member member)
{
return accepts(member.getModifiers());
}
//~ Fields ............................................................
private int mask = 0;
private boolean reverseMask = false;
};
//~ Object Creation Methods ...............................................
// ----------------------------------------------------------
/**
* 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)
{
for (ClassLoader loader : getCandidateLoaders())
{
try
{
return loader.loadClass(className);
}
catch (ClassNotFoundException e)
{
// Ignore it and try the next loader
}
}
// Class wasn't found in any candidate loader
fail("cannot find class " + className);
// Unreachable, so just to make the compiler happy:
return null;
}
// ----------------------------------------------------------
/**
* 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
* @param <T> The generic parameter T is deduced from the provided
* constructor
* @return The newly created object
*/
public static <T> T create(Constructor<T> constructor, Object ... params)
{
T 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);
}
//~ Method Invocation Methods .............................................
// ----------------------------------------------------------
/**
* 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;
}
//~ Field Manipulation Methods ............................................
// ----------------------------------------------------------
/**
* Look up a field by name, receiver class, and type, finding the
* field that will accept the given type (not requiring
* an exact match on type). Any errors are thrown as instances of
* {@link ReflectionError}.
* It looks up fields that are declared in the specified class,
* as well as inherited classes. It sets up accessibility if the field
* is not public.
* @param receiverClass The class of the receiver
* @param type The type of this field
* @param fieldName The name of the field
* @return The corresponding Field
*/
public static Field getField(
Class<?> receiverClass, Class<?> type, String fieldName)
{
Field field = null;
Class<?> declaringClass = receiverClass;
// TODO: This approach will not find public fields declared in
// implemented interfaces.
while (field == null && declaringClass != null)
{
try
{
field = declaringClass.getDeclaredField(fieldName);
}
catch (NoSuchFieldException e)
{
// check parent class
declaringClass = declaringClass.getSuperclass();
}
}
if (field == null)
{
fail("Cannot find field " + fieldName
+ " in " + simpleClassName(receiverClass));
}
if (!actualMatchesFormal(field.getType(), type))
{
String msg = "Field " + fieldName + " in class ";
if (declaringClass == receiverClass)
{
msg += simpleClassName(declaringClass);
}
else
{
msg += simpleClassName(receiverClass) + " (inherited from "
+ simpleClassName(declaringClass) + ")";
}
fail(msg + " is declared of type "
+ simpleClassName(field.getType())
+ ", not "
+ simpleClassName(type)
+ ".");
}
//check and set access permission
if (!field.isAccessible())
{
final Field theField = field;
AccessController.doPrivileged( new PrivilegedAction<Void>()
{
public Void run()
{
theField.setAccessible(true);
return null;
}
});
}
return field;
}
//------------------------------------------------------------------
/**
* Get the value of a field. The field is looked up a field by name,
* receiver object and type, finding the field that will accept the
* given type (not requiring an exact match on type). It turns any
* errors into ReflectionErrors with appropriate hint messages.
* @param receiver The object containing the field
* @param type The type of this field
* @param fieldName The name of the field
* @param <T> The generic parameter T is deduced from the returnType
* @return The value corresponding Field
*/
public static <T> T get(Object receiver, Class<T> type, String fieldName )
{
assert fieldName != null : "fieldName cannot be null";
assert !fieldName.isEmpty() : "fieldName cannot be empty";
assert receiver != null : "Receiver object cannot be empty";
Field field = getField(receiver.getClass(), type, fieldName);
Object fieldValue = null;
if (field != null)
{
try
{
fieldValue = field.get(receiver);
}
catch (IllegalArgumentException e)
{
fail(field.getName() + " in "
+ simpleClassName(receiver.getClass())
+ " cannot be retrieved.");
}
catch (IllegalAccessException e)
{
// Shouldn't happen, since setAccessible() was called,
// but the compiler requires a handler.
fail(e.getMessage());
}
}
@SuppressWarnings("unchecked")
T value = (T)fieldValue;
return value;
}
//------------------------------------------------------------------
/**
* Get the value of a field. The field is looked up a field by name,
* receiver object and type, finding the field that will accept the
* given type (not requiring an exact match on type). It turns any
* errors into ReflectionErrors with appropriate hint messages.
* Note that the field must be a static field.
* @param receiverClass The class of the receiver
* @param type The type of this field
* @param fieldName The name of the field
* @param <T> The generic parameter T is deduced from the returnType
* @return The value corresponding Field
*/
public static <T> T get(
Class<?> receiverClass, Class<T> type, String fieldName)
{
assert fieldName != null : "fieldName cannot be null";
assert !fieldName.isEmpty() : "fieldName cannot be empty";
assert receiverClass != null : "Receiver object cannot be empty";
Field field = getField(receiverClass, type, fieldName);
Object fieldValue = null;
if (field != null)
{
try
{
fieldValue = field.get(null);
}
catch (IllegalArgumentException e)
{
fail(field.getName() + " in "
+ simpleClassName(receiverClass)
+ " cannot be retrieved.");
}
catch (IllegalAccessException e)
{
// Shouldn't happen, since setAccessible() was called,
// but the compiler requires a handler.
fail(e.getMessage());
}
}
@SuppressWarnings("unchecked")
T value = (T)fieldValue;
return value;
}
//------------------------------------------------------------------
/**
* Sets value of a field.
* @param receiver The object of the receiver
* @param fieldName The name of the field
* @param value The value to set in the field
*/
public static void set(Object receiver, String fieldName, Object value)
{
assert fieldName != null : "fieldName cannot be null";
assert !fieldName.isEmpty() : "fieldName cannot be empty";
assert receiver != null : "Receiver object cannot be empty";
Field field =
getField(receiver.getClass(), value.getClass(), fieldName);
if (field != null)
{
try
{
field.set(receiver, value);
}
catch (IllegalArgumentException e)
{
fail(field.getName() +" of type "
+ simpleClassName(field.getType())
+" cannot be assigned a value of "
+ value
+ ((value == null)
? ""
: "(of type "
+ simpleClassName(value.getClass())
+ ")")
+ ".");
}
catch (IllegalAccessException e)
{
// Shouldn't happen, since setAccessible() was called,
// but the compiler requires a handler.
fail(e.getMessage());
}
}
}
//------------------------------------------------------------------
/**
* Sets value of a field.
* @param receiverClass The class of the receiver
* @param fieldName The name of the field
* @param value The value will be set in the field
*/
public static void set(
Class<?> receiverClass, String fieldName, Object value)
{
assert fieldName != null : "fieldName cannot be null";
assert !fieldName.isEmpty() : "fieldName cannot be empty";
assert receiverClass != null : "Receiver object cannot be empty";
Field field = getField(receiverClass, value.getClass(), fieldName);
if (field != null)
{
try
{
field.set(null, value);
}
catch (IllegalArgumentException e)
{
fail(field.getName() +" of type "
+ simpleClassName(field.getType())
+" cannot be assigned a value of "
+ value
+ ((value == null)
? ""
: "(of type "
+ simpleClassName(value.getClass())
+ ")")
+ ".");
}
catch (IllegalAccessException e)
{
// Shouldn't happen, since setAccessible() was called,
// but the compiler requires a handler.
fail(e.getMessage());
}
}
}
//~ Public Utility Methods ................................................
// simple printing 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 is useful 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();
}
// argument matching methods --------------------------------
// ----------------------------------------------------------
/**
* 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;
}
// ----------------------------------------------------------
/**
* Determine whether it is appropriate to attempt to "auto-box" an
* actual argument of type <code>actual</code> into a formal parameter
* type <code>formal</code>.
* @param actual The type of the actual value.
* @param formal The type of the formal parameter.
* @return True if it is appropriate to auto-box the actual into the
* type of the formal.
*/
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) );
}
//~ Private Utility Methods ...............................................
//-----------------------------------------------------------------
/**
* Throws a ReflectionError with the given message if the given
* condition is not true.
* @param message The message will be used to create ReflectionError
* @param condition The condition to check
*/
private static void assertTrue(String message, boolean condition)
{
if (!condition)
{
fail(message, 2);
}
}
//-----------------------------------------------------------------
/**
* Throws a ReflectionError with the given message.
* @param message The message will be used to create ReflectionError
*/
private static void fail(String message)
{
fail(message, 2);
}
//-----------------------------------------------------------------
/**
* Throws a ReflectionError with the given message.
* @param message The message will be used to create ReflectionError
* @param stackFramesToStrip The number of levels to strip from the
* top of the stack frame (e.g., 1 will strip the call to fail() itself).
*/
private static void fail(String message, int stackFramesToStrip)
{
ReflectionError error = new ReflectionError(message);
StackTraceElement[] trace = error.getStackTrace();
// remove the call to fail() from the stack trace
error.setStackTrace(java.util.Arrays.copyOfRange(
trace, stackFramesToStrip, trace.length));
throw error;
}
//-----------------------------------------------------------------
/**
* Return an array of potential classloaders to use to look up classes.
* This array contains three loaders, including the loader
* used to load ReflectionSupport itself, the current thread's
* context classloader, and the classloader used to load the nearest
* non-library class in the method calling sequence. These loaders
* are arranged in order from most-specific to least-specific,
* in terms of delegation (i.e., if any ancestor/descendant delegation
* relationships exist among the loaders, the descendant appears before
* the ancestor in the returned array).
*/
private static ClassLoader[] getCandidateLoaders()
{
ClassLoader[] result = new ClassLoader[] {
RESOLVER.getNonlibraryCallerClass().getClassLoader(),
Thread.currentThread().getContextClassLoader(),
ReflectionSupport.class.getClassLoader()
};
// Sort them in most-specific-to-least-specific order
// using a simple bubble sort (augh!!)
boolean moved = false;
if (hasAsChild(result[1], result[2]))
{
ClassLoader temp = result[2];
result[2] = result[1];
result[1] = temp;
moved = true;
}
if (hasAsChild(result[0], result[1]))
{
ClassLoader temp = result[1];
result[1] = result[0];
result[0] = temp;
moved = true;
}
if (moved)
{
if (hasAsChild(result[1], result[2]))
{
ClassLoader temp = result[2];
result[2] = result[1];
result[1] = temp;
}
}
else
{
if (hasAsChild(result[0], result[2]))
{
ClassLoader temp = result[2];
result[2] = result[0];
result[0] = temp;
}
}
return result;
}
//-----------------------------------------------------------------
/**
* Returns true if 'loader2' is a delegation child of 'loader1' or if
* 'loader1' == 'loader2'. Of course, this works only for classloaders that
* set their parent pointers correctly. 'null' is interpreted as the
* primordial loader (i.e., everybody's parent).
*/
private static boolean hasAsChild(ClassLoader loader1, ClassLoader loader2)
{
if (loader1 == loader2)
{
return true;
}
if (loader2 == null)
{
return false;
}
if (loader1 == null)
{
return true;
}
for ( ; loader2 != null; loader2 = loader2.getParent())
{
if (loader2 == loader1)
{
return true;
}
}
return false;
}
//-----------------------------------------------------------------
/**
* This interface represents a strategy for finding the nearest
* non-library class in the calling sequence.
*/
private static interface CallerResolver
{
Class<?> getNonlibraryCallerClass();
}
//-----------------------------------------------------------------
/**
* A stock implementation of CallerResolver that uses features from
* the SecurityManager class to get access to the declaring classes of
* methods on the call stack. Note that this class need NOT be installed
* as the current security manager at all (and shouldn't be). It
* simply uses inherited features to implement the CallerResolver
* interface.
*/
private static class SecurityManagerCallerResolver
extends SecurityManager
implements CallerResolver
{
public Class<?> getNonlibraryCallerClass()
{
Class<?>[] stack = getClassContext();
for (Class<?> c : getClassContext())
{
boolean isClientClass = true;
String name = c.getName();
for (String prefix : STACK_FILTERS)
{
if (name.startsWith(prefix))
{
isClientClass = false;
}
break;
}
if (isClientClass)
{
return c;
}
}
if (stack.length > 0)
{
// If no non-library classes were found, return the bottom
// of stack
return stack[stack.length - 1];
}
else
{
// If there's no stack (!), just return this class
return ReflectionSupport.class;
}
}
}
//~ Private Fields ........................................................
private static /*final*/ CallerResolver RESOLVER;
static
{
try
{
// this can fail if the current SecurityManager does not allow
// RuntimePermission ("createSecurityManager"):
AccessController.doPrivileged( new PrivilegedAction<Void>()
{
public Void run()
{
RESOLVER = new SecurityManagerCallerResolver();
return null;
}
});
}
catch (SecurityException e)
{
System.out.println("Warning: " + ReflectionSupport.class
+ " could not create CallerResolver:");
e.printStackTrace();
RESOLVER = new CallerResolver()
{
public java.lang.Class<?> getNonlibraryCallerClass()
{
return ReflectionSupport.class;
};
};
}
}
private static final String[] STACK_FILTERS = {
"student.",
"cs1705.",
// JUnit 4 support:
"org.junit.",
// JUnit 3 support:
"junit.",
"java.",
"sun.",
"org.apache.tools.ant.",
// Web-CAT infrastructure
"net.sf.webcat."
};
}