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