1/*
2 * Copyright (c) 2007 Mockito contributors
3 * This program is made available under the terms of the MIT License.
4 */
5package org.mockito.internal.stubbing.answers;
6
7import org.mockito.exceptions.Reporter;
8import org.mockito.invocation.InvocationOnMock;
9import org.mockito.stubbing.Answer;
10
11import java.io.Serializable;
12
13/**
14 * Returns the passed parameter identity at specified index.
15 *
16 * <p>The <code>argumentIndex</code> represents the index in the argument array of the invocation.</p>
17 * <p>If this number equals -1 then the last argument is returned.</p>
18 *
19 * @see org.mockito.AdditionalAnswers
20 * @since 1.9.5
21 */
22public class ReturnsArgumentAt implements Answer<Object>, Serializable {
23
24    private static final long serialVersionUID = -589315085166295101L;
25
26    public static final int LAST_ARGUMENT = -1;
27
28    private final int wantedArgumentPosition;
29
30    /**
31     * Build the identity answer to return the argument at the given position in the argument array.
32     *
33     * @param wantedArgumentPosition The position of the argument identity to return in the invocation.
34     *                      Using <code>-1</code> indicates the last argument.
35     */
36    public ReturnsArgumentAt(int wantedArgumentPosition) {
37        this.wantedArgumentPosition = checkWithinAllowedRange(wantedArgumentPosition);
38    }
39
40    public Object answer(InvocationOnMock invocation) throws Throwable {
41        validateIndexWithinInvocationRange(invocation);
42        return invocation.getArguments()[actualArgumentPosition(invocation)];
43    }
44
45
46    private int actualArgumentPosition(InvocationOnMock invocation) {
47        return returningLastArg() ?
48                lastArgumentIndexOf(invocation) :
49                argumentIndexOf(invocation);
50    }
51
52    private boolean returningLastArg() {
53        return wantedArgumentPosition == LAST_ARGUMENT;
54    }
55
56    private int argumentIndexOf(InvocationOnMock invocation) {
57        return wantedArgumentPosition;
58    }
59
60    private int lastArgumentIndexOf(InvocationOnMock invocation) {
61        return invocation.getArguments().length - 1;
62    }
63
64    private int checkWithinAllowedRange(int argumentPosition) {
65        if (argumentPosition != LAST_ARGUMENT && argumentPosition < 0) {
66            new Reporter().invalidArgumentRangeAtIdentityAnswerCreationTime();
67        }
68        return argumentPosition;
69    }
70
71    public int wantedArgumentPosition() {
72        return wantedArgumentPosition;
73    }
74
75    public void validateIndexWithinInvocationRange(InvocationOnMock invocation) {
76        if (!argumentPositionInRange(invocation)) {
77            new Reporter().invalidArgumentPositionRangeAtInvocationTime(invocation,
78                                                                        returningLastArg(),
79                                                                        wantedArgumentPosition);
80        }
81    }
82
83    private boolean argumentPositionInRange(InvocationOnMock invocation) {
84        int actualArgumentPosition = actualArgumentPosition(invocation);
85        if (actualArgumentPosition < 0) {
86            return false;
87        }
88        if (!invocation.getMethod().isVarArgs()) {
89            return invocation.getArguments().length > actualArgumentPosition;
90        }
91        // for all varargs accepts positive ranges
92        return true;
93    }
94
95    public Class returnedTypeOnSignature(InvocationOnMock invocation) {
96        int actualArgumentPosition = actualArgumentPosition(invocation);
97
98        if(!invocation.getMethod().isVarArgs()) {
99            return invocation.getMethod().getParameterTypes()[actualArgumentPosition];
100        }
101
102        Class<?>[] parameterTypes = invocation.getMethod().getParameterTypes();
103        int varargPosition = parameterTypes.length - 1;
104
105        if(actualArgumentPosition < varargPosition) {
106            return parameterTypes[actualArgumentPosition];
107        } else {
108            return parameterTypes[varargPosition].getComponentType();
109        }
110    }
111}
112