1
2
3
4
5 package mockit.internal.expectations;
6
7 import edu.umd.cs.findbugs.annotations.NonNull;
8 import edu.umd.cs.findbugs.annotations.Nullable;
9
10 import java.util.ArrayList;
11 import java.util.List;
12
13 import mockit.internal.expectations.invocation.ExpectedInvocation;
14 import mockit.internal.expectations.invocation.InvocationArguments;
15 import mockit.internal.state.TestRun;
16 import mockit.internal.util.GeneratedClasses;
17
18 final class PhasedExecutionState {
19 @NonNull
20 final List<Expectation> expectations;
21 @NonNull
22 final List<VerifiedExpectation> verifiedExpectations;
23 @NonNull
24 final EquivalentInstances equivalentInstances;
25 @NonNull
26 final InstanceBasedMatching instanceBasedMatching;
27 @Nullable
28 PartiallyMockedInstances partiallyMockedInstances;
29
30 PhasedExecutionState() {
31 expectations = new ArrayList<>();
32 verifiedExpectations = new ArrayList<>();
33 equivalentInstances = new EquivalentInstances();
34 instanceBasedMatching = new InstanceBasedMatching();
35 }
36
37 void addExpectation(@NonNull Expectation expectation) {
38 ExpectedInvocation invocation = expectation.invocation;
39 forceMatchingOnMockInstanceIfRequired(invocation);
40 removeMatchingExpectationsCreatedBefore(invocation);
41 expectations.add(expectation);
42 }
43
44 private void forceMatchingOnMockInstanceIfRequired(@NonNull ExpectedInvocation invocation) {
45 if (!invocation.matchInstance
46 && isToBeMatchedOnInstance(invocation.instance, invocation.getMethodNameAndDescription())) {
47 invocation.matchInstance = true;
48 }
49 }
50
51 boolean isToBeMatchedOnInstance(@Nullable Object mock, @NonNull String mockNameAndDesc) {
52 if (mock == null || mockNameAndDesc.charAt(0) == '<') {
53 return false;
54 }
55
56 if (instanceBasedMatching.isToBeMatchedOnInstance(mock)
57 || partiallyMockedInstances != null && partiallyMockedInstances.isToBeMatchedOnInstance(mock)) {
58 return true;
59 }
60
61 return TestRun.getExecutingTest().isInjectableMock(mock);
62 }
63
64 private void removeMatchingExpectationsCreatedBefore(@NonNull ExpectedInvocation invocation) {
65 Expectation previousExpectation = findPreviousExpectation(invocation);
66
67 if (previousExpectation != null) {
68 expectations.remove(previousExpectation);
69 invocation.copyDefaultReturnValue(previousExpectation.invocation);
70 }
71 }
72
73 @Nullable
74 private Expectation findPreviousExpectation(@NonNull ExpectedInvocation newInvocation) {
75 int n = expectations.size();
76
77 if (n == 0) {
78 return null;
79 }
80
81 Object mock = newInvocation.instance;
82 @NonNull
83 Boolean matchInstance = newInvocation.matchInstance;
84 String mockClassDesc = newInvocation.getClassDesc();
85 String mockNameAndDesc = newInvocation.getMethodNameAndDescription();
86 boolean isConstructor = newInvocation.isConstructor();
87
88 for (Expectation previous : expectations) {
89 if (isMatchingInvocation(mock, matchInstance, mockClassDesc, mockNameAndDesc, isConstructor, previous)
90 && isWithMatchingArguments(newInvocation, previous.invocation)) {
91 return previous;
92 }
93 }
94
95 return null;
96 }
97
98 private boolean isMatchingInvocation(@Nullable Object mock, @Nullable Boolean matchInstance,
99 @NonNull String mockClassDesc, @NonNull String mockNameAndDesc, boolean constructorInvocation,
100 @NonNull Expectation expectation) {
101 ExpectedInvocation invocation = expectation.invocation;
102
103 return invocation.isMatch(mock, mockClassDesc, mockNameAndDesc) && isSameMockedClass(mock, invocation.instance)
104 && (constructorInvocation || mock == null || isMatchingInstance(mock, matchInstance, expectation));
105 }
106
107 private static boolean isSameMockedClass(@Nullable Object mock1, @Nullable Object mock2) {
108 if (mock1 == mock2) {
109 return true;
110 }
111
112 if (mock1 != null && mock2 != null) {
113 Class<?> mockedClass1 = mock1.getClass();
114 Class<?> mockedClass2 = GeneratedClasses.getMockedClass(mock2);
115 return mockedClass2.isAssignableFrom(mockedClass1)
116 || TestRun.mockFixture().areCapturedClasses(mockedClass1, mockedClass2);
117 }
118
119 return false;
120 }
121
122 private boolean isWithMatchingArguments(@NonNull ExpectedInvocation newInvocation,
123 @NonNull ExpectedInvocation previousInvocation) {
124 InvocationArguments newArguments = newInvocation.arguments;
125 InvocationArguments previousArguments = previousInvocation.arguments;
126
127 if (newArguments.getMatchers() == null) {
128 return previousArguments.isMatch(newArguments.getValues(), equivalentInstances.instanceMap);
129 }
130
131 return newArguments.hasEquivalentMatchers(previousArguments);
132 }
133
134 @Nullable
135 Expectation findExpectation(@Nullable Object mock, @NonNull String mockClassDesc, @NonNull String mockNameAndDesc,
136 @NonNull Object[] args) {
137 boolean isConstructor = mockNameAndDesc.charAt(0) == '<';
138 Expectation replayExpectationFound = null;
139
140
141
142 for (Expectation expectation : expectations) {
143 if (replayExpectationFound != null && expectation.recordPhase == null) {
144 continue;
145 }
146
147 if (isMatchingInvocation(mock, null, mockClassDesc, mockNameAndDesc, isConstructor, expectation)
148 && expectation.invocation.arguments.isMatch(args, equivalentInstances.instanceMap)) {
149 if (expectation.recordPhase == null) {
150 replayExpectationFound = expectation;
151 continue;
152 }
153
154 if (isConstructor) {
155 equivalentInstances.registerReplacementInstanceIfApplicable(mock, expectation.invocation);
156 }
157
158 return expectation;
159 }
160 }
161
162 return replayExpectationFound;
163 }
164
165 private boolean isMatchingInstance(@NonNull Object invokedInstance, @Nullable Boolean matchInstance,
166 @NonNull Expectation expectation) {
167 ExpectedInvocation invocation = expectation.invocation;
168 Object invocationInstance = invocation.instance;
169 assert invocationInstance != null;
170
171 if (equivalentInstances.isEquivalentInstance(invocationInstance, invokedInstance)) {
172 return true;
173 }
174
175 if (TestRun.getExecutingTest().isInjectableMock(invokedInstance)
176 || partiallyMockedInstances != null
177 && partiallyMockedInstances.isDynamicMockInstanceOrClass(invokedInstance, invocationInstance)
178 || equivalentInstances.areNonEquivalentInstances(invocationInstance, invokedInstance)) {
179 return false;
180 }
181
182 return (matchInstance == null || !matchInstance) && !invocation.matchInstance && expectation.recordPhase != null
183 && !equivalentInstances.replacementMap.containsValue(invocationInstance);
184 }
185 }