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.handler;
6
7import org.mockito.exceptions.Reporter;
8import org.mockito.internal.InternalMockHandler;
9import org.mockito.internal.invocation.InvocationMatcher;
10import org.mockito.internal.invocation.MatchersBinder;
11import org.mockito.internal.progress.MockingProgress;
12import org.mockito.internal.progress.ThreadSafeMockingProgress;
13import org.mockito.internal.stubbing.*;
14import org.mockito.internal.verification.MockAwareVerificationMode;
15import org.mockito.internal.verification.VerificationDataImpl;
16import org.mockito.invocation.Invocation;
17import org.mockito.mock.MockCreationSettings;
18import org.mockito.stubbing.Answer;
19import org.mockito.stubbing.VoidMethodStubbable;
20import org.mockito.verification.VerificationMode;
21
22import java.util.List;
23
24/**
25 * Invocation handler set on mock objects.
26 *
27 * @param <T>
28 *            type of mock object to handle
29 */
30class MockHandlerImpl<T> implements InternalMockHandler<T> {
31
32    private static final long serialVersionUID = -2917871070982574165L;
33
34    InvocationContainerImpl invocationContainerImpl;
35    MatchersBinder matchersBinder = new MatchersBinder();
36    MockingProgress mockingProgress = new ThreadSafeMockingProgress();
37
38    private final MockCreationSettings mockSettings;
39
40    public MockHandlerImpl(MockCreationSettings mockSettings) {
41        this.mockSettings = mockSettings;
42        this.mockingProgress = new ThreadSafeMockingProgress();
43        this.matchersBinder = new MatchersBinder();
44        this.invocationContainerImpl = new InvocationContainerImpl(mockingProgress, mockSettings);
45    }
46
47    public Object handle(Invocation invocation) throws Throwable {
48		if (invocationContainerImpl.hasAnswersForStubbing()) {
49            // stubbing voids with stubVoid() or doAnswer() style
50            InvocationMatcher invocationMatcher = matchersBinder.bindMatchers(
51                    mockingProgress.getArgumentMatcherStorage(),
52                    invocation
53            );
54            invocationContainerImpl.setMethodForStubbing(invocationMatcher);
55            return null;
56        }
57        VerificationMode verificationMode = mockingProgress.pullVerificationMode();
58
59        InvocationMatcher invocationMatcher = matchersBinder.bindMatchers(
60                mockingProgress.getArgumentMatcherStorage(),
61                invocation
62        );
63
64        mockingProgress.validateState();
65
66        // if verificationMode is not null then someone is doing verify()
67        if (verificationMode != null) {
68            // We need to check if verification was started on the correct mock
69            // - see VerifyingWithAnExtraCallToADifferentMockTest (bug 138)
70            if (((MockAwareVerificationMode) verificationMode).getMock() == invocation.getMock()) {
71                VerificationDataImpl data = createVerificationData(invocationContainerImpl, invocationMatcher);
72                verificationMode.verify(data);
73                return null;
74            } else {
75                // this means there is an invocation on a different mock. Re-adding verification mode
76                // - see VerifyingWithAnExtraCallToADifferentMockTest (bug 138)
77                mockingProgress.verificationStarted(verificationMode);
78            }
79        }
80
81        // prepare invocation for stubbing
82        invocationContainerImpl.setInvocationForPotentialStubbing(invocationMatcher);
83        OngoingStubbingImpl<T> ongoingStubbing = new OngoingStubbingImpl<T>(invocationContainerImpl);
84        mockingProgress.reportOngoingStubbing(ongoingStubbing);
85
86        // look for existing answer for this invocation
87        StubbedInvocationMatcher stubbedInvocation = invocationContainerImpl.findAnswerFor(invocation);
88
89        if (stubbedInvocation != null) {
90            stubbedInvocation.captureArgumentsFrom(invocation);
91            return stubbedInvocation.answer(invocation);
92        } else {
93             Object ret = mockSettings.getDefaultAnswer().answer(invocation);
94
95            // redo setting invocation for potential stubbing in case of partial
96            // mocks / spies.
97            // Without it, the real method inside 'when' might have delegated
98            // to other self method and overwrite the intended stubbed method
99            // with a different one. The reset is required to avoid runtime exception that validates return type with stubbed method signature.
100            invocationContainerImpl.resetInvocationForPotentialStubbing(invocationMatcher);
101            return ret;
102        }
103	}
104
105    public VoidMethodStubbable<T> voidMethodStubbable(T mock) {
106        return new VoidMethodStubbableImpl<T>(mock, invocationContainerImpl);
107    }
108
109    public MockCreationSettings getMockSettings() {
110        return mockSettings;
111    }
112
113    @SuppressWarnings("unchecked")
114    public void setAnswersForStubbing(List<Answer> answers) {
115        invocationContainerImpl.setAnswersForStubbing(answers);
116    }
117
118    public InvocationContainer getInvocationContainer() {
119        return invocationContainerImpl;
120    }
121
122    private VerificationDataImpl createVerificationData(InvocationContainerImpl invocationContainerImpl, InvocationMatcher invocationMatcher) {
123        if (mockSettings.isStubOnly()) {
124            new Reporter().stubPassedToVerify();     // this throws an exception
125        }
126
127        return new VerificationDataImpl(invocationContainerImpl, invocationMatcher);
128    }
129}
130
131