1
2
3
4
5 package mockit.internal.classGeneration;
6
7 import static java.util.Arrays.asList;
8
9 import static mockit.asm.jvmConstants.Opcodes.ALOAD;
10 import static mockit.asm.jvmConstants.Opcodes.INVOKESPECIAL;
11
12 import edu.umd.cs.findbugs.annotations.NonNull;
13 import edu.umd.cs.findbugs.annotations.Nullable;
14
15 import java.lang.reflect.Method;
16 import java.lang.reflect.Type;
17 import java.util.ArrayList;
18 import java.util.HashSet;
19 import java.util.List;
20 import java.util.Set;
21
22 import mockit.asm.classes.ClassInfo;
23 import mockit.asm.classes.ClassReader;
24 import mockit.asm.fields.FieldVisitor;
25 import mockit.asm.jvmConstants.Access;
26 import mockit.asm.metadata.ClassMetadataReader;
27 import mockit.asm.metadata.ClassMetadataReader.MethodInfo;
28 import mockit.asm.methods.MethodVisitor;
29 import mockit.asm.types.JavaType;
30 import mockit.internal.BaseClassModifier;
31 import mockit.internal.ClassFile;
32 import mockit.internal.util.TypeDescriptor;
33
34 public class BaseSubclassGenerator extends BaseClassModifier {
35 private static final int CLASS_ACCESS_MASK = 0xFFFF - Access.ABSTRACT;
36 private static final int CONSTRUCTOR_ACCESS_MASK = Access.PUBLIC + Access.PROTECTED;
37
38
39 @NonNull
40 Class<?> baseClass;
41 @NonNull
42 private final String subclassName;
43 @Nullable
44 protected final MockedTypeInfo mockedTypeInfo;
45 private final boolean copyConstructors;
46
47
48 @NonNull
49 private final List<String> implementedMethods;
50 @Nullable
51 private String superClassOfSuperClass;
52 private Set<String> superInterfaces;
53
54 protected BaseSubclassGenerator(@NonNull Class<?> baseClass, @NonNull ClassReader cr,
55 @Nullable Type genericMockedType, @NonNull String subclassName, boolean copyConstructors) {
56 super(cr);
57 this.baseClass = baseClass;
58 this.subclassName = subclassName.replace('.', '/');
59 mockedTypeInfo = genericMockedType == null ? null : new MockedTypeInfo(genericMockedType);
60 this.copyConstructors = copyConstructors;
61 implementedMethods = new ArrayList<>();
62 }
63
64 @Override
65 public void visit(int version, int access, @NonNull String name, @NonNull ClassInfo additionalInfo) {
66 ClassInfo subClassInfo = new ClassInfo();
67 subClassInfo.superName = name;
68 subClassInfo.signature = mockedTypeInfo == null ? additionalInfo.signature
69 : mockedTypeInfo.implementationSignature;
70 int subclassAccess = access & CLASS_ACCESS_MASK | Access.FINAL;
71
72 super.visit(version, subclassAccess, subclassName, subClassInfo);
73
74 superClassOfSuperClass = additionalInfo.superName;
75 superInterfaces = new HashSet<>();
76
77 String[] interfaces = additionalInfo.interfaces;
78
79 if (interfaces.length > 0) {
80 superInterfaces.addAll(asList(interfaces));
81 }
82 }
83
84 @Override
85 public final void visitInnerClass(@NonNull String name, @Nullable String outerName, @Nullable String innerName,
86 int access) {
87 }
88
89 @Override
90 @Nullable
91 public final FieldVisitor visitField(int access, @NonNull String name, @NonNull String desc,
92 @Nullable String signature, @Nullable Object value) {
93 return null;
94 }
95
96 @Override
97 @Nullable
98 public MethodVisitor visitMethod(int access, @NonNull String name, @NonNull String desc, @Nullable String signature,
99 @Nullable String[] exceptions) {
100 if (copyConstructors && "<init>".equals(name)) {
101 if ((access & CONSTRUCTOR_ACCESS_MASK) != 0) {
102 generateConstructorDelegatingToSuper(desc, signature, exceptions);
103 }
104 } else {
105
106 generateImplementationIfAbstractMethod(superClassName, access, name, desc, signature, exceptions);
107 }
108
109 return null;
110 }
111
112 private void generateConstructorDelegatingToSuper(@NonNull String desc, @Nullable String signature,
113 @Nullable String[] exceptions) {
114 mw = cw.visitMethod(Access.PUBLIC, "<init>", desc, signature, exceptions);
115 mw.visitVarInsn(ALOAD, 0);
116 int varIndex = 1;
117
118 for (JavaType paramType : JavaType.getArgumentTypes(desc)) {
119 int loadOpcode = paramType.getLoadOpcode();
120 mw.visitVarInsn(loadOpcode, varIndex);
121 varIndex++;
122 }
123
124 mw.visitMethodInsn(INVOKESPECIAL, superClassName, "<init>", desc, false);
125 generateEmptyImplementation();
126 }
127
128 private void generateImplementationIfAbstractMethod(String className, int access, @NonNull String name,
129 @NonNull String desc, @Nullable String signature, @Nullable String[] exceptions) {
130 if (!"<init>".equals(name)) {
131 String methodNameAndDesc = name + desc;
132
133 if (!implementedMethods.contains(methodNameAndDesc)) {
134 if ((access & Access.ABSTRACT) != 0) {
135 generateMethodImplementation(className, access, name, desc, signature, exceptions);
136 }
137
138 implementedMethods.add(methodNameAndDesc);
139 }
140 }
141 }
142
143 protected void generateMethodImplementation(String className, int access, @NonNull String name,
144 @NonNull String desc, @Nullable String signature, @Nullable String[] exceptions) {
145 }
146
147 @Override
148 public void visitEnd() {
149 generateImplementationsForInheritedAbstractMethods(superClassOfSuperClass);
150
151 while (!superInterfaces.isEmpty()) {
152 String superInterface = superInterfaces.iterator().next();
153 generateImplementationsForAbstractMethods(superInterface, false);
154 superInterfaces.remove(superInterface);
155 }
156 }
157
158 private void generateImplementationsForInheritedAbstractMethods(@Nullable String superName) {
159 if (superName != null) {
160 generateImplementationsForAbstractMethods(superName, true);
161 }
162 }
163
164 private void generateImplementationsForAbstractMethods(@NonNull String typeName, boolean abstractClass) {
165 if (!"java/lang/Object".equals(typeName)) {
166 byte[] typeBytecode = ClassFile.getClassFile(typeName);
167 ClassMetadataReader cmr = new ClassMetadataReader(typeBytecode);
168 String[] interfaces = cmr.getInterfaces();
169
170 if (interfaces != null) {
171 superInterfaces.addAll(asList(interfaces));
172 }
173
174 for (MethodInfo method : cmr.getMethods()) {
175 if (abstractClass) {
176 generateImplementationIfAbstractMethod(typeName, method.accessFlags, method.name, method.desc, null,
177 null);
178 } else if (method.isAbstract()) {
179 generateImplementationForInterfaceMethodIfMissing(typeName, method);
180 }
181 }
182
183 if (abstractClass) {
184 String superClass = cmr.getSuperClass();
185 generateImplementationsForInheritedAbstractMethods(superClass);
186 }
187 }
188 }
189
190 private void generateImplementationForInterfaceMethodIfMissing(@NonNull String typeName,
191 @NonNull MethodInfo method) {
192 String name = method.name;
193 String desc = method.desc;
194 String methodNameAndDesc = name + desc;
195
196 if (!implementedMethods.contains(methodNameAndDesc)) {
197 if (!hasMethodImplementation(name, desc)) {
198 generateMethodImplementation(typeName, method.accessFlags, name, desc, null, null);
199 }
200
201 implementedMethods.add(methodNameAndDesc);
202 }
203 }
204
205 private boolean hasMethodImplementation(@NonNull String name, @NonNull String desc) {
206 Class<?>[] paramTypes = TypeDescriptor.getParameterTypes(desc);
207
208 try {
209 Method method = baseClass.getMethod(name, paramTypes);
210 return !method.getDeclaringClass().isInterface();
211 } catch (NoSuchMethodException ignore) {
212 return false;
213 }
214 }
215 }