blob: 7c0a144b716745e468e4da88328bd78c91129fe4 [file] [log] [blame]
/*
* Copyright (c) 2007 Mockito contributors
* This program is made available under the terms of the MIT License.
*/
package org.mockito.internal.stubbing.answers;
import java.io.Serializable;
import java.lang.reflect.Method;
import org.mockito.internal.exceptions.Reporter;
import org.mockito.invocation.Invocation;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.mockito.stubbing.ValidableAnswer;
/**
* Returns the passed parameter identity at specified index.
* <p>
* <p>The <code>argumentIndex</code> represents the index in the argument array of the invocation.</p>
* <p>If this number equals -1 then the last argument is returned.</p>
*
* @see org.mockito.AdditionalAnswers
* @since 1.9.5
*/
public class ReturnsArgumentAt implements Answer<Object>, ValidableAnswer, Serializable {
private static final long serialVersionUID = -589315085166295101L;
public static final int LAST_ARGUMENT = -1;
private final int wantedArgumentPosition;
/**
* Build the identity answer to return the argument at the given position in the argument array.
*
* @param wantedArgumentPosition The position of the argument identity to return in the invocation.
* Using <code>-1</code> indicates the last argument ({@link #LAST_ARGUMENT}).
*/
public ReturnsArgumentAt(int wantedArgumentPosition) {
if (wantedArgumentPosition != LAST_ARGUMENT && wantedArgumentPosition < 0) {
throw Reporter.invalidArgumentRangeAtIdentityAnswerCreationTime();
}
this.wantedArgumentPosition = wantedArgumentPosition;
}
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
int argumentPosition = inferWantedArgumentPosition(invocation);
validateIndexWithinInvocationRange(invocation, argumentPosition);
if (wantedArgIndexIsVarargAndSameTypeAsReturnType(invocation.getMethod(), argumentPosition)) {
// answer raw vararg array argument
return ((Invocation) invocation).getRawArguments()[argumentPosition];
} else {
// answer expanded argument at wanted position
return invocation.getArgument(argumentPosition);
}
}
@Override
public void validateFor(InvocationOnMock invocation) {
int argumentPosition = inferWantedArgumentPosition(invocation);
validateIndexWithinInvocationRange(invocation, argumentPosition);
validateArgumentTypeCompatibility(invocation, argumentPosition);
}
private int inferWantedArgumentPosition(InvocationOnMock invocation) {
return wantedArgumentPosition == LAST_ARGUMENT ?
invocation.getArguments().length - 1 :
wantedArgumentPosition;
}
private void validateIndexWithinInvocationRange(InvocationOnMock invocation, int argumentPosition) {
if (!wantedArgumentPositionIsValidForInvocation(invocation, argumentPosition)) {
throw Reporter.invalidArgumentPositionRangeAtInvocationTime(invocation,
wantedArgumentPosition == LAST_ARGUMENT,
wantedArgumentPosition);
}
}
private void validateArgumentTypeCompatibility(InvocationOnMock invocation, int argumentPosition) {
InvocationInfo invocationInfo = new InvocationInfo(invocation);
Class<?> inferredArgumentType = inferWantedArgumentType(invocation, argumentPosition);
if (!invocationInfo.isValidReturnType(inferredArgumentType)) {
throw Reporter.wrongTypeOfArgumentToReturn(invocation,
invocationInfo.printMethodReturnType(),
inferredArgumentType,
wantedArgumentPosition);
}
}
private boolean wantedArgIndexIsVarargAndSameTypeAsReturnType(Method method, int argumentPosition) {
Class<?>[] parameterTypes = method.getParameterTypes();
return method.isVarArgs()
&& argumentPosition == /* vararg index */ parameterTypes.length - 1
&& method.getReturnType().isAssignableFrom(parameterTypes[argumentPosition]);
}
private boolean wantedArgumentPositionIsValidForInvocation(InvocationOnMock invocation, int argumentPosition) {
if (argumentPosition < 0) {
return false;
}
if (!invocation.getMethod().isVarArgs()) {
return invocation.getArguments().length > argumentPosition;
}
// for all varargs accepts positive ranges
return true;
}
private Class<?> inferWantedArgumentType(InvocationOnMock invocation, int argumentPosition) {
Class<?>[] parameterTypes = invocation.getMethod().getParameterTypes();
// Easy when the method is not a vararg
if (!invocation.getMethod().isVarArgs()) {
return parameterTypes[argumentPosition];
}
// Now for varargs
int varargIndex = parameterTypes.length - 1; // vararg always last
if (argumentPosition < varargIndex) {
// Same for non vararg arguments
return parameterTypes[argumentPosition];
} else {
// if wanted argument is vararg
if (wantedArgIndexIsVarargAndSameTypeAsReturnType(invocation.getMethod(), argumentPosition)) {
// return the vararg array if return type is compatible
// because the user probably want to return the array itself if the return type is compatible
return parameterTypes[argumentPosition]; // move to MethodInfo ?
} else {
// return the type in this vararg array
return parameterTypes[varargIndex].getComponentType();
}
}
}
}