• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2007 Mockito contributors
3  * This program is made available under the terms of the MIT License.
4  */
5 package org.mockito.internal.stubbing.answers;
6 
7 import static org.mockito.internal.exceptions.Reporter.invalidArgumentPositionRangeAtInvocationTime;
8 import static org.mockito.internal.exceptions.Reporter.invalidArgumentRangeAtIdentityAnswerCreationTime;
9 import static org.mockito.internal.exceptions.Reporter.wrongTypeOfArgumentToReturn;
10 
11 import java.io.Serializable;
12 import java.lang.reflect.Method;
13 import org.mockito.invocation.Invocation;
14 import org.mockito.invocation.InvocationOnMock;
15 import org.mockito.stubbing.Answer;
16 import org.mockito.stubbing.ValidableAnswer;
17 
18 /**
19  * Returns the passed parameter identity at specified index.
20  * <p>
21  * <p>
22  * The <code>argumentIndex</code> represents the index in the argument array of the invocation.
23  * </p>
24  * <p>
25  * If this number equals -1 then the last argument is returned.
26  * </p>
27  *
28  * @see org.mockito.AdditionalAnswers
29  * @since 1.9.5
30  */
31 public class ReturnsArgumentAt implements Answer<Object>, ValidableAnswer, Serializable {
32 
33     private static final long serialVersionUID = -589315085166295101L;
34 
35     public static final int LAST_ARGUMENT = -1;
36 
37     private final int wantedArgumentPosition;
38 
39     /**
40      * Build the identity answer to return the argument at the given position in the argument array.
41      *
42      * @param wantedArgumentPosition
43      *            The position of the argument identity to return in the invocation. Using <code>-1</code> indicates the last argument ({@link #LAST_ARGUMENT}).
44      */
ReturnsArgumentAt(int wantedArgumentPosition)45     public ReturnsArgumentAt(int wantedArgumentPosition) {
46         if (wantedArgumentPosition != LAST_ARGUMENT && wantedArgumentPosition < 0) {
47             throw invalidArgumentRangeAtIdentityAnswerCreationTime();
48         }
49         this.wantedArgumentPosition = wantedArgumentPosition;
50     }
51 
52     @Override
answer(InvocationOnMock invocation)53     public Object answer(InvocationOnMock invocation) throws Throwable {
54         int argumentPosition = inferWantedArgumentPosition(invocation);
55         validateIndexWithinInvocationRange(invocation, argumentPosition);
56 
57         if (wantedArgIndexIsVarargAndSameTypeAsReturnType(invocation.getMethod(), argumentPosition)) {
58             // answer raw vararg array argument
59             return ((Invocation) invocation).getRawArguments()[argumentPosition];
60         }
61 
62         // answer expanded argument at wanted position
63         return invocation.getArgument(argumentPosition);
64 
65     }
66 
67     @Override
validateFor(InvocationOnMock invocation)68     public void validateFor(InvocationOnMock invocation) {
69         int argumentPosition = inferWantedArgumentPosition(invocation);
70         validateIndexWithinInvocationRange(invocation, argumentPosition);
71         validateArgumentTypeCompatibility((Invocation) invocation, argumentPosition);
72     }
73 
inferWantedArgumentPosition(InvocationOnMock invocation)74     private int inferWantedArgumentPosition(InvocationOnMock invocation) {
75         if (wantedArgumentPosition == LAST_ARGUMENT)
76             return invocation.getArguments().length - 1;
77 
78         return wantedArgumentPosition;
79     }
80 
validateIndexWithinInvocationRange(InvocationOnMock invocation, int argumentPosition)81     private void validateIndexWithinInvocationRange(InvocationOnMock invocation, int argumentPosition) {
82         if (!wantedArgumentPositionIsValidForInvocation(invocation, argumentPosition)) {
83             throw invalidArgumentPositionRangeAtInvocationTime(invocation,
84                                                                wantedArgumentPosition == LAST_ARGUMENT,
85                                                                wantedArgumentPosition);
86         }
87     }
88 
validateArgumentTypeCompatibility(Invocation invocation, int argumentPosition)89     private void validateArgumentTypeCompatibility(Invocation invocation, int argumentPosition) {
90         InvocationInfo invocationInfo = new InvocationInfo(invocation);
91 
92         Class<?> inferredArgumentType = inferArgumentType(invocation, argumentPosition);
93 
94         if (!invocationInfo.isValidReturnType(inferredArgumentType)){
95             throw wrongTypeOfArgumentToReturn(invocation,
96                                               invocationInfo.printMethodReturnType(),
97                                               inferredArgumentType,
98                                               wantedArgumentPosition);
99         }
100     }
101 
wantedArgIndexIsVarargAndSameTypeAsReturnType(Method method, int argumentPosition)102     private boolean wantedArgIndexIsVarargAndSameTypeAsReturnType(Method method, int argumentPosition) {
103         Class<?>[] parameterTypes = method.getParameterTypes();
104         return method.isVarArgs() &&
105               argumentPosition == /* vararg index */ parameterTypes.length - 1 &&
106               method.getReturnType().isAssignableFrom(parameterTypes[argumentPosition]);
107     }
108 
wantedArgumentPositionIsValidForInvocation(InvocationOnMock invocation, int argumentPosition)109     private boolean wantedArgumentPositionIsValidForInvocation(InvocationOnMock invocation, int argumentPosition) {
110         if (argumentPosition < 0) {
111             return false;
112         }
113         if (!invocation.getMethod().isVarArgs()) {
114             return invocation.getArguments().length > argumentPosition;
115         }
116         // for all varargs accepts positive ranges
117         return true;
118     }
119 
inferArgumentType(Invocation invocation, int argumentIndex)120     private Class<?> inferArgumentType(Invocation invocation, int argumentIndex) {
121         Class<?>[] parameterTypes = invocation.getMethod().getParameterTypes();
122 
123         // Easy when the method is not a vararg
124         if (!invocation.getMethod().isVarArgs()) {
125             Class<?> argumentType = parameterTypes[argumentIndex];
126             Object argumentValue = invocation.getArgument(argumentIndex);
127             // we don't want to return primitive wrapper types
128             if (argumentType.isPrimitive() || argumentValue == null) {
129                 return argumentType;
130             }
131 
132             return argumentValue.getClass();
133         }
134 
135         // Now for varargs
136         int varargIndex = parameterTypes.length - 1; // vararg always last
137 
138         if (argumentIndex < varargIndex) {
139             // Same for non vararg arguments
140             return parameterTypes[argumentIndex];
141         }
142         // if wanted argument is vararg
143         if (wantedArgIndexIsVarargAndSameTypeAsReturnType(invocation.getMethod(), argumentIndex)) {
144             // return the vararg array if return type is compatible
145             // because the user probably want to return the array itself if the return type is compatible
146             return parameterTypes[argumentIndex]; // move to MethodInfo ?
147         }
148         // return the type in this vararg array
149         return parameterTypes[varargIndex].getComponentType();
150 
151     }
152 }
153 
154