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.ACONST_NULL;
8   import static mockit.asm.jvmConstants.Opcodes.ALOAD;
9   import static mockit.asm.jvmConstants.Opcodes.SIPUSH;
10  import static mockit.internal.util.TypeConversionBytecode.generateCastOrUnboxing;
11  import static mockit.internal.util.TypeConversionBytecode.isPrimitiveWrapper;
12  
13  import edu.umd.cs.findbugs.annotations.NonNull;
14  import edu.umd.cs.findbugs.annotations.Nullable;
15  
16  import mockit.asm.methods.MethodWriter;
17  import mockit.asm.types.ArrayType;
18  import mockit.asm.types.JavaType;
19  import mockit.asm.types.ObjectType;
20  import mockit.asm.types.ReferenceType;
21  
22  import org.checkerframework.checker.index.qual.NonNegative;
23  
24  final class Capture {
25      @NonNull
26      private final InvocationBlockModifier invocationBlockModifier;
27      @NonNull
28      private final MethodWriter mw;
29      @NonNegative
30      private final int opcode;
31      @NonNegative
32      private final int varIndex;
33      @Nullable
34      private String typeToCapture;
35      @NonNegative
36      private int parameterIndex;
37      @NonNegative
38      private boolean parameterIndexFixed;
39  
40      Capture(@NonNull InvocationBlockModifier invocationBlockModifier, @NonNegative int opcode,
41              @NonNegative int varIndex, @Nullable String typeToCapture, @NonNegative int parameterIndex) {
42          this.invocationBlockModifier = invocationBlockModifier;
43          mw = invocationBlockModifier.getMethodWriter();
44          this.opcode = opcode;
45          this.varIndex = varIndex;
46          this.typeToCapture = typeToCapture;
47          this.parameterIndex = parameterIndex;
48      }
49  
50      Capture(@NonNull InvocationBlockModifier invocationBlockModifier, @NonNegative int varIndex,
51              @NonNegative int parameterIndex) {
52          this.invocationBlockModifier = invocationBlockModifier;
53          mw = invocationBlockModifier.getMethodWriter();
54          opcode = ALOAD;
55          this.varIndex = varIndex;
56          this.parameterIndex = parameterIndex;
57      }
58  
59      /**
60       * Generates bytecode that will be responsible for performing the following steps: 1. Get the argument value (an
61       * Object) for the last matched invocation. 2. Cast to a reference type or unbox to a primitive type, as needed. 3.
62       * Store the converted value in its local variable.
63       */
64      void generateCodeToStoreCapturedValue() {
65          if (opcode != ALOAD) {
66              mw.visitIntInsn(SIPUSH, parameterIndex);
67  
68              if (typeToCapture == null) {
69                  mw.visitInsn(ACONST_NULL);
70              } else {
71                  mw.visitLdcInsn(typeToCapture);
72              }
73  
74              invocationBlockModifier.generateCallToActiveInvocationsMethod("matchedArgument",
75                      "(ILjava/lang/String;)Ljava/lang/Object;");
76  
77              JavaType argType = getArgumentType();
78              generateCastOrUnboxing(mw, argType, opcode);
79  
80              mw.visitVarInsn(opcode, varIndex);
81          }
82      }
83  
84      @NonNull
85      private JavaType getArgumentType() {
86          if (typeToCapture == null) {
87              return invocationBlockModifier.argumentMatching.getParameterType(parameterIndex);
88          }
89  
90          if (typeToCapture.charAt(0) == '[') {
91              return ArrayType.create(typeToCapture);
92          }
93  
94          return ObjectType.create(typeToCapture);
95      }
96  
97      boolean fixParameterIndex(@NonNegative int originalIndex, @NonNegative int newIndex) {
98          if (!parameterIndexFixed && parameterIndex == originalIndex) {
99              parameterIndex = newIndex;
100             parameterIndexFixed = true;
101             return true;
102         }
103 
104         return false;
105     }
106 
107     void generateCallToSetArgumentTypeIfNeeded() {
108         if (opcode == ALOAD) {
109             mw.visitIntInsn(SIPUSH, parameterIndex);
110             mw.visitLdcInsn(varIndex);
111             invocationBlockModifier.generateCallToActiveInvocationsMethod("setExpectedArgumentType", "(II)V");
112         } else if (typeToCapture != null && !isTypeToCaptureSameAsParameterType(typeToCapture)) {
113             mw.visitIntInsn(SIPUSH, parameterIndex);
114             mw.visitLdcInsn(typeToCapture);
115             invocationBlockModifier.generateCallToActiveInvocationsMethod("setExpectedArgumentType",
116                     "(ILjava/lang/String;)V");
117         }
118     }
119 
120     private boolean isTypeToCaptureSameAsParameterType(@NonNull String typeDesc) {
121         JavaType parameterType = invocationBlockModifier.argumentMatching.getParameterType(parameterIndex);
122 
123         if (parameterType instanceof ReferenceType) {
124             String parameterTypeDesc = ((ReferenceType) parameterType).getInternalName();
125             return typeDesc.equals(parameterTypeDesc);
126         }
127 
128         return isPrimitiveWrapper(typeDesc);
129     }
130 }