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.transformation;
6   
7   import static mockit.asm.jvmConstants.Opcodes.GETFIELD;
8   import static mockit.asm.jvmConstants.Opcodes.SIPUSH;
9   
10  import edu.umd.cs.findbugs.annotations.NonNull;
11  
12  import mockit.asm.methods.MethodWriter;
13  import mockit.asm.types.JavaType;
14  
15  import org.checkerframework.checker.index.qual.NonNegative;
16  
17  final class ArgumentMatching {
18      private static final JavaType[] NO_PARAMETERS = {};
19      private static final String ANY_FIELDS = "any anyString anyInt anyBoolean anyLong anyDouble anyFloat anyChar anyShort anyByte";
20      private static final String WITH_METHODS = "with(Lmockit/Delegate;)Ljava/lang/Object; "
21              + "withAny(Ljava/lang/Object;)Ljava/lang/Object; "
22              + "withArgThat(Lorg/hamcrest/Matcher;)Ljava/lang/Object; "
23              + "withCapture()Ljava/lang/Object; withCapture(Ljava/util/List;)Ljava/lang/Object; "
24              + "withCapture(Ljava/lang/Object;)Ljava/util/List; "
25              + "withEqual(Ljava/lang/Object;)Ljava/lang/Object; withEqual(DD)D withEqual(FD)F "
26              + "withInstanceLike(Ljava/lang/Object;)Ljava/lang/Object; "
27              + "withInstanceOf(Ljava/lang/Class;)Ljava/lang/Object; "
28              + "withNotEqual(Ljava/lang/Object;)Ljava/lang/Object; "
29              + "withNull()Ljava/lang/Object; withNotNull()Ljava/lang/Object; "
30              + "withSameInstance(Ljava/lang/Object;)Ljava/lang/Object; "
31              + "withSubstring(Ljava/lang/CharSequence;)Ljava/lang/CharSequence; "
32              + "withPrefix(Ljava/lang/CharSequence;)Ljava/lang/CharSequence; "
33              + "withSuffix(Ljava/lang/CharSequence;)Ljava/lang/CharSequence; "
34              + "withMatch(Ljava/lang/CharSequence;)Ljava/lang/CharSequence;";
35  
36      @NonNull
37      private final InvocationBlockModifier modifier;
38  
39      // Helper fields that allow argument matchers to be moved to the correct positions of their corresponding
40      // parameters:
41      @NonNull
42      private final int[] matcherStacks;
43      @NonNegative
44      private int matcherCount;
45      @NonNull
46      private JavaType[] parameterTypes;
47  
48      static boolean isAnyField(@NonNull String name) {
49          return name.startsWith("any") && ANY_FIELDS.contains(name);
50      }
51  
52      static boolean isCallToArgumentMatcher(@NonNull String name, @NonNull String desc) {
53          return name.startsWith("with") && WITH_METHODS.contains(name + desc);
54      }
55  
56      ArgumentMatching(@NonNull InvocationBlockModifier modifier) {
57          this.modifier = modifier;
58          matcherStacks = new int[40];
59          parameterTypes = NO_PARAMETERS;
60      }
61  
62      void addMatcher(@NonNegative int stackSize) {
63          matcherStacks[matcherCount++] = stackSize;
64      }
65  
66      @NonNegative
67      int getMatcherCount() {
68          return matcherCount;
69      }
70  
71      @NonNull
72      JavaType getParameterType(@NonNegative int parameterIndex) {
73          return parameterTypes[parameterIndex];
74      }
75  
76      void generateCodeToAddArgumentMatcherForAnyField(@NonNull String fieldOwner, @NonNull String name,
77              @NonNull String desc) {
78          MethodWriter mw = modifier.getMethodWriter();
79          mw.visitFieldInsn(GETFIELD, fieldOwner, name, desc);
80          modifier.generateCallToActiveInvocationsMethod(name);
81      }
82  
83      boolean handleInvocationParameters(@NonNegative int stackSize, @NonNull String desc) {
84          parameterTypes = JavaType.getArgumentTypes(desc);
85          int stackAfter = stackSize - getSumOfParameterSizes();
86          boolean mockedInvocationUsingTheMatchers = stackAfter < matcherStacks[0];
87  
88          if (mockedInvocationUsingTheMatchers) {
89              generateCallsToMoveArgMatchers(stackAfter);
90              modifier.argumentCapturing.generateCallsToSetArgumentTypesToCaptureIfAny();
91              matcherCount = 0;
92          }
93  
94          return mockedInvocationUsingTheMatchers;
95      }
96  
97      @NonNegative
98      private int getSumOfParameterSizes() {
99          @NonNegative
100         int sum = 0;
101 
102         for (JavaType argType : parameterTypes) {
103             sum += argType.getSize();
104         }
105 
106         return sum;
107     }
108 
109     private void generateCallsToMoveArgMatchers(@NonNegative int initialStack) {
110         @NonNegative
111         int stack = initialStack;
112         @NonNegative
113         int nextMatcher = 0;
114         @NonNegative
115         int matcherStack = matcherStacks[0];
116 
117         for (int i = 0; i < parameterTypes.length && nextMatcher < matcherCount; i++) {
118             stack += parameterTypes[i].getSize();
119 
120             if (stack == matcherStack || stack == matcherStack + 1) {
121                 if (nextMatcher < i) {
122                     generateCallToMoveArgMatcher(nextMatcher, i);
123                     modifier.argumentCapturing.updateCaptureIfAny(nextMatcher, i);
124                 }
125 
126                 nextMatcher++;
127                 matcherStack = matcherStacks[nextMatcher];
128             }
129         }
130     }
131 
132     private void generateCallToMoveArgMatcher(@NonNegative int originalMatcherIndex, @NonNegative int toIndex) {
133         MethodWriter mw = modifier.getMethodWriter();
134         mw.visitIntInsn(SIPUSH, originalMatcherIndex);
135         mw.visitIntInsn(SIPUSH, toIndex);
136         modifier.generateCallToActiveInvocationsMethod("moveArgMatcher", "(II)V");
137     }
138 }