/* Copyright (c) 2000-2004 jMock.org
*/
package org.test4j.tools.reflector.imposteriser;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import ext.test4j.hamcrest.Description;
import ext.test4j.hamcrest.SelfDescribing;
import ext.test4j.hamcrest.StringDescription;
/**
* The static details about a method and the run-time details of its invocation.
*
* @since 1.0
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public class Invocation implements SelfDescribing {
public static final Object[] NO_PARAMETERS = null;
private final Object invokedObject;
private final Method invokedMethod;
private final Object[] parameterValues;
// A kludge but there doesn't seem to be a way to find this out through the
// reflection API.
private static final Map<Class, Class> BOX_TYPES = new HashMap<Class, Class>() {
private static final long serialVersionUID = 7820000823306263259L;
{
put(boolean.class, Boolean.class);
put(byte.class, Byte.class);
put(char.class, Character.class);
put(short.class, Short.class);
put(int.class, Integer.class);
put(long.class, Long.class);
put(float.class, Float.class);
put(double.class, Double.class);
}
};
public Invocation(Object invoked, Method method, Object... parameterValues) {
this.invokedObject = invoked;
this.invokedMethod = method;
this.parameterValues = (parameterValues == NO_PARAMETERS) ? new Object[0] : parameterValues.clone();
}
@Override
public String toString() {
return super.toString() + "[" + StringDescription.toString(this) + "]";
}
@Override
public boolean equals(Object other) {
return (other instanceof Invocation) && this.equals((Invocation) other);
}
public boolean equals(Invocation other) {
return other != null && invokedObject == other.invokedObject && invokedMethod.equals(other.invokedMethod)
&& Arrays.equals(parameterValues, other.parameterValues);
}
@Override
public int hashCode() {
return invokedObject.hashCode() ^ invokedMethod.hashCode() ^ Arrays.hashCode(parameterValues);
}
public void describeTo(Description description) {
description.appendText(invokedObject.toString());
description.appendText(".");
description.appendText(invokedMethod.getName());
description.appendValueList("(", ", ", ")", parameterValues);
}
public Object getInvokedObject() {
return invokedObject;
}
public Method getInvokedMethod() {
return invokedMethod;
}
public int getParameterCount() {
return parameterValues.length;
}
public Object getParameter(int i) {
return parameterValues[i];
}
public Object[] getParametersAsArray() {
return parameterValues.clone();
}
public Object applyTo(Object target) throws Throwable {
try {
return invokedMethod.invoke(target, getParametersAsArray());
} catch (InvocationTargetException ex) {
throw ex.getTargetException();
}
}
public void checkReturnTypeCompatibility(final Object value) {
Class returnType = invokedMethod.getReturnType();
if (returnType == void.class) {
failIfReturnTypeIsNotNull(value);
} else if (value == null) {
failIfReturnTypeIsPrimitive();
} else {
Class valueType = value.getClass();
if (!isCompatible(returnType, valueType)) {
reportTypeError(returnType, valueType);
}
}
}
private boolean isCompatible(Class returnType, Class valueType) {
if (returnType.isPrimitive()) {
// The reflection API doesn't reflect Java's auto-boxing.
return isBoxedType(returnType, valueType);
}
return returnType.isAssignableFrom(valueType);
}
private boolean isBoxedType(Class primitiveType, Class referenceType) {
return BOX_TYPES.get(primitiveType) == referenceType;
}
private void failIfReturnTypeIsNotNull(final Object result) {
if (result != null) {
throw new IllegalStateException("tried to return a value from a void method: " + result);
}
}
private void failIfReturnTypeIsPrimitive() {
Class returnType = invokedMethod.getReturnType();
if (returnType.isPrimitive()) {
throw new IllegalStateException("tried to return null value from method returning " + returnType.getName());
}
}
private void reportTypeError(Class returnType, Class valueType) {
throw new IllegalStateException("tried to return a " + valueType.getName()
+ " from a method that can only return a " + returnType.getName());
}
}