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