View Javadoc
1   /*
2    * MIT License
3    * Copyright (c) 2006-2025 JMockit developers
4    * See LICENSE file for full license text.
5    */
6   package mockit.internal.expectations;
7   
8   import edu.umd.cs.findbugs.annotations.NonNull;
9   import edu.umd.cs.findbugs.annotations.Nullable;
10  
11  import java.util.ArrayList;
12  import java.util.List;
13  import java.util.Map;
14  
15  import mockit.internal.expectations.invocation.ExpectedInvocation;
16  import mockit.internal.expectations.invocation.InvocationConstraints;
17  import mockit.internal.expectations.invocation.UnexpectedInvocation;
18  
19  final class ReplayPhase extends Phase {
20      @NonNull
21      final FailureState failureState;
22      @NonNull
23      final List<Expectation> invocations;
24      @NonNull
25      final List<Object> invocationInstances;
26      @NonNull
27      final List<Object[]> invocationArguments;
28  
29      ReplayPhase(@NonNull PhasedExecutionState executionState, @NonNull FailureState failureState) {
30          super(executionState);
31          this.failureState = failureState;
32          invocations = new ArrayList<>();
33          invocationInstances = new ArrayList<>();
34          invocationArguments = new ArrayList<>();
35      }
36  
37      @Override
38      @Nullable
39      Object handleInvocation(@Nullable Object mock, int mockAccess, @NonNull String mockClassDesc,
40              @NonNull String mockNameAndDesc, @Nullable String genericSignature, boolean withRealImpl,
41              @NonNull Object[] args) throws Throwable {
42          Expectation expectation = executionState.findExpectation(mock, mockClassDesc, mockNameAndDesc, args);
43          Object replacementInstance = mock == null ? null
44                  : executionState.equivalentInstances.getReplacementInstanceForMethodInvocation(mock, mockNameAndDesc);
45  
46          if (expectation == null) {
47              expectation = createExpectation(replacementInstance == null ? mock : replacementInstance, mockAccess,
48                      mockClassDesc, mockNameAndDesc, genericSignature, args);
49          } else if (expectation.recordPhase != null) {
50              registerNewInstanceAsEquivalentToOneFromRecordedConstructorInvocation(mock, expectation.invocation);
51          }
52  
53          invocations.add(expectation);
54          invocationInstances.add(mock);
55          invocationArguments.add(args);
56          expectation.constraints.incrementInvocationCount();
57  
58          return produceResult(expectation, mock, withRealImpl, args);
59      }
60  
61      @NonNull
62      private Expectation createExpectation(@Nullable Object mock, int mockAccess, @NonNull String mockClassDesc,
63              @NonNull String mockNameAndDesc, @Nullable String genericSignature, @NonNull Object[] args) {
64          ExpectedInvocation invocation = new ExpectedInvocation(mock, mockAccess, mockClassDesc, mockNameAndDesc, false,
65                  genericSignature, args);
66          Expectation expectation = new Expectation(invocation);
67          executionState.addExpectation(expectation);
68          return expectation;
69      }
70  
71      private void registerNewInstanceAsEquivalentToOneFromRecordedConstructorInvocation(@Nullable Object mock,
72              @NonNull ExpectedInvocation invocation) {
73          if (mock != null && invocation.isConstructor()) {
74              Map<Object, Object> instanceMap = getInstanceMap();
75              instanceMap.put(mock, invocation.instance);
76          }
77      }
78  
79      @Nullable
80      private Object produceResult(@NonNull Expectation expectation, @Nullable Object mock, boolean withRealImpl,
81              @NonNull Object[] args) throws Throwable {
82          boolean executeRealImpl = withRealImpl && expectation.recordPhase == null;
83  
84          if (executeRealImpl) {
85              expectation.executedRealImplementation = true;
86              return Void.class;
87          }
88  
89          if (expectation.constraints.isInvocationCountMoreThanMaximumExpected()) {
90              UnexpectedInvocation unexpectedInvocation = expectation.invocation.errorForUnexpectedInvocation(args);
91              failureState.setErrorThrown(unexpectedInvocation);
92              return null;
93          }
94  
95          return expectation.produceResult(mock, args);
96      }
97  
98      @Nullable
99      Error endExecution() {
100         return getErrorForFirstExpectationThatIsMissing();
101     }
102 
103     @Nullable
104     private Error getErrorForFirstExpectationThatIsMissing() {
105         List<Expectation> notStrictExpectations = executionState.expectations;
106 
107         // New expectations might get added to the list, so a regular loop would cause a CME.
108         for (Expectation notStrict : notStrictExpectations) {
109             InvocationConstraints constraints = notStrict.constraints;
110 
111             if (constraints.isInvocationCountLessThanMinimumExpected()) {
112                 List<ExpectedInvocation> nonMatchingInvocations = getNonMatchingInvocations(notStrict);
113                 return constraints.errorForMissingExpectations(notStrict.invocation, nonMatchingInvocations);
114             }
115         }
116 
117         return null;
118     }
119 
120     @NonNull
121     private List<ExpectedInvocation> getNonMatchingInvocations(@NonNull Expectation unsatisfiedExpectation) {
122         ExpectedInvocation unsatisfiedInvocation = unsatisfiedExpectation.invocation;
123         List<ExpectedInvocation> nonMatchingInvocations = new ArrayList<>();
124 
125         for (Expectation replayedExpectation : invocations) {
126             ExpectedInvocation replayedInvocation = replayedExpectation.invocation;
127 
128             if (replayedExpectation != unsatisfiedExpectation && replayedInvocation.isMatch(unsatisfiedInvocation)) {
129                 nonMatchingInvocations.add(replayedInvocation);
130             }
131         }
132 
133         return nonMatchingInvocations;
134     }
135 }