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