View Javadoc
1   /*
2    * MIT License
3    * Copyright (c) 2006-2025 JMockit developers
4    * See LICENSE file for full license text.
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                 // noinspection AssignmentToMethodParameter
137                 desc = subInterfaceOverride.substring(name.length());
138                 // noinspection AssignmentToMethodParameter
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 }