1
2
3
4
5
6 package mockit.internal.expectations.mocking;
7
8 import static java.lang.reflect.Modifier.isStatic;
9
10 import static mockit.asm.jvmConstants.Access.FINAL;
11 import static mockit.asm.jvmConstants.Access.PUBLIC;
12 import static mockit.asm.jvmConstants.Access.isSynthetic;
13 import static mockit.asm.jvmConstants.Opcodes.ALOAD;
14 import static mockit.asm.jvmConstants.Opcodes.INVOKESPECIAL;
15
16 import edu.umd.cs.findbugs.annotations.NonNull;
17 import edu.umd.cs.findbugs.annotations.Nullable;
18
19 import java.lang.reflect.Type;
20 import java.util.ArrayList;
21 import java.util.EnumSet;
22 import java.util.List;
23
24 import mockit.asm.annotations.AnnotationVisitor;
25 import mockit.asm.classes.ClassInfo;
26 import mockit.asm.classes.ClassReader;
27 import mockit.asm.fields.FieldVisitor;
28 import mockit.asm.metadata.ClassMetadataReader;
29 import mockit.asm.metadata.ClassMetadataReader.Attribute;
30 import mockit.asm.metadata.ClassMetadataReader.MethodInfo;
31 import mockit.asm.methods.MethodVisitor;
32 import mockit.internal.BaseClassModifier;
33 import mockit.internal.ClassFile;
34 import mockit.internal.classGeneration.MockedTypeInfo;
35 import mockit.internal.reflection.GenericTypeReflection;
36 import mockit.internal.reflection.GenericTypeReflection.GenericSignature;
37
38 public final class InterfaceImplementationGenerator extends BaseClassModifier {
39 private static final int CLASS_ACCESS = PUBLIC + FINAL;
40 private static final EnumSet<Attribute> SIGNATURE = EnumSet.of(Attribute.Signature);
41
42 @NonNull
43 private final MockedTypeInfo mockedTypeInfo;
44 @NonNull
45 private final String implementationClassDesc;
46 @NonNull
47 private final List<String> implementedMethods;
48 private String interfaceName;
49 private String methodOwner;
50 @Nullable
51 private String[] initialSuperInterfaces;
52
53 public InterfaceImplementationGenerator(@NonNull ClassReader cr, @NonNull Type mockedType,
54 @NonNull String implementationClassName) {
55 super(cr);
56 mockedTypeInfo = new MockedTypeInfo(mockedType);
57 implementationClassDesc = implementationClassName.replace('.', '/');
58 implementedMethods = new ArrayList<>();
59 }
60
61 @Override
62 public void visit(int version, int access, @NonNull String name, @NonNull ClassInfo additionalInfo) {
63 interfaceName = name;
64 methodOwner = name;
65 initialSuperInterfaces = additionalInfo.interfaces;
66
67 ClassInfo implementationClassInfo = new ClassInfo();
68 String signature = additionalInfo.signature;
69 implementationClassInfo.signature = signature == null ? null
70 : signature + mockedTypeInfo.implementationSignature;
71 implementationClassInfo.interfaces = new String[] { name };
72 implementationClassInfo.superName = additionalInfo.superName;
73
74 super.visit(version, CLASS_ACCESS, implementationClassDesc, implementationClassInfo);
75
76 generateNoArgsConstructor();
77 }
78
79 private void generateNoArgsConstructor() {
80 mw = cw.visitMethod(PUBLIC, "<init>", "()V", null, null);
81 mw.visitVarInsn(ALOAD, 0);
82 mw.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
83 generateEmptyImplementation();
84 }
85
86 @Override
87 public AnnotationVisitor visitAnnotation(@NonNull String desc) {
88 return null;
89 }
90
91 @Override
92 public void visitInnerClass(@NonNull String name, String outerName, String innerName, int access) {
93 }
94
95 @Nullable
96 @Override
97 public FieldVisitor visitField(int access, @NonNull String name, @NonNull String desc, @Nullable String signature,
98 @Nullable Object value) {
99 return null;
100 }
101
102 @Nullable
103 @Override
104 public MethodVisitor visitMethod(int access, @NonNull String name, @NonNull String desc, @Nullable String signature,
105 @Nullable String[] exceptions) {
106 if (!isSynthetic(access)) {
107 generateMethodImplementation(access, name, desc, signature, exceptions);
108 }
109
110 return null;
111 }
112
113 private void generateMethodImplementation(int access, @NonNull String name, @NonNull String desc,
114 @Nullable String signature, @Nullable String[] exceptions) {
115 if (!isStatic(access)) {
116 String methodNameAndDesc = name + desc;
117
118 if (!implementedMethods.contains(methodNameAndDesc)) {
119 generateMethodBody(access, name, desc, signature, exceptions);
120 implementedMethods.add(methodNameAndDesc);
121 }
122 }
123 }
124
125 private void generateMethodBody(int access, @NonNull String name, @NonNull String desc, @Nullable String signature,
126 @Nullable String[] exceptions) {
127 mw = cw.visitMethod(PUBLIC, name, desc, signature, exceptions);
128
129 String className = null;
130
131 if (signature != null) {
132 String subInterfaceOverride = getSubInterfaceOverride(mockedTypeInfo.genericTypeMap, name, signature);
133
134 if (subInterfaceOverride != null) {
135 className = interfaceName;
136
137 desc = subInterfaceOverride.substring(name.length());
138
139 signature = null;
140 }
141 }
142
143 if (className == null) {
144 className = isOverrideOfMethodFromSuperInterface(name, desc) ? interfaceName : methodOwner;
145 }
146
147 generateDirectCallToHandler(className, access, name, desc, signature);
148 generateReturnWithObjectAtTopOfTheStack(desc);
149 mw.visitMaxStack(1);
150 }
151
152 @Nullable
153 private String getSubInterfaceOverride(@NonNull GenericTypeReflection genericTypeMap, @NonNull String name,
154 @NonNull String genericSignature) {
155 if (!implementedMethods.isEmpty()) {
156 GenericSignature parsedSignature = genericTypeMap.parseSignature(genericSignature);
157
158 for (String implementedMethod : implementedMethods) {
159 if (sameMethodName(implementedMethod, name) && parsedSignature.satisfiesSignature(implementedMethod)) {
160 return implementedMethod;
161 }
162 }
163 }
164
165 return null;
166 }
167
168 private static boolean sameMethodName(@NonNull String implementedMethod, @NonNull String name) {
169 return implementedMethod.startsWith(name) && implementedMethod.charAt(name.length()) == '(';
170 }
171
172 private boolean isOverrideOfMethodFromSuperInterface(@NonNull String name, @NonNull String desc) {
173 if (!implementedMethods.isEmpty()) {
174 int p = desc.lastIndexOf(')');
175 String descNoReturnType = desc.substring(0, p + 1);
176
177 for (String implementedMethod : implementedMethods) {
178 if (sameMethodName(implementedMethod, name) && implementedMethod.contains(descNoReturnType)) {
179 return true;
180 }
181 }
182 }
183
184 return false;
185 }
186
187 @Override
188 public void visitEnd() {
189 assert initialSuperInterfaces != null;
190
191 for (String superInterface : initialSuperInterfaces) {
192 generateImplementationsForInterfaceMethodsRecurringToSuperInterfaces(superInterface);
193 }
194 }
195
196 private void generateImplementationsForInterfaceMethodsRecurringToSuperInterfaces(@NonNull String anInterface) {
197 methodOwner = anInterface;
198
199 byte[] interfaceBytecode = ClassFile.getClassFile(anInterface);
200 ClassMetadataReader cmr = new ClassMetadataReader(interfaceBytecode, SIGNATURE);
201 String[] superInterfaces = cmr.getInterfaces();
202
203 for (MethodInfo method : cmr.getMethods()) {
204 generateMethodImplementation(method.accessFlags, method.name, method.desc, method.signature, null);
205 }
206
207 if (superInterfaces != null) {
208 for (String superInterface : superInterfaces) {
209 generateImplementationsForInterfaceMethodsRecurringToSuperInterfaces(superInterface);
210 }
211 }
212 }
213 }