mockit.internal.classGeneration.BaseSubclassGenerator Maven / Gradle / Ivy
/*
* Copyright (c) 2006 JMockit developers
* This file is subject to the terms of the MIT license (see LICENSE.txt).
*/
package mockit.internal.classGeneration;
import static java.util.Arrays.asList;
import static mockit.asm.jvmConstants.Opcodes.ALOAD;
import static mockit.asm.jvmConstants.Opcodes.INVOKESPECIAL;
import edu.umd.cs.findbugs.annotations.NonNull;
import edu.umd.cs.findbugs.annotations.Nullable;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import mockit.asm.classes.ClassInfo;
import mockit.asm.classes.ClassReader;
import mockit.asm.fields.FieldVisitor;
import mockit.asm.jvmConstants.Access;
import mockit.asm.metadata.ClassMetadataReader;
import mockit.asm.metadata.ClassMetadataReader.MethodInfo;
import mockit.asm.methods.MethodVisitor;
import mockit.asm.types.JavaType;
import mockit.internal.BaseClassModifier;
import mockit.internal.ClassFile;
import mockit.internal.util.TypeDescriptor;
public class BaseSubclassGenerator extends BaseClassModifier {
private static final int CLASS_ACCESS_MASK = 0xFFFF - Access.ABSTRACT;
private static final int CONSTRUCTOR_ACCESS_MASK = Access.PUBLIC + Access.PROTECTED;
// Fixed initial state:
@NonNull
Class> baseClass;
@NonNull
private final String subclassName;
@Nullable
protected final MockedTypeInfo mockedTypeInfo;
private final boolean copyConstructors;
// Helper fields for mutable state:
@NonNull
private final List implementedMethods;
@Nullable
private String superClassOfSuperClass;
private Set superInterfaces;
protected BaseSubclassGenerator(@NonNull Class> baseClass, @NonNull ClassReader cr,
@Nullable Type genericMockedType, @NonNull String subclassName, boolean copyConstructors) {
super(cr);
this.baseClass = baseClass;
this.subclassName = subclassName.replace('.', '/');
mockedTypeInfo = genericMockedType == null ? null : new MockedTypeInfo(genericMockedType);
this.copyConstructors = copyConstructors;
implementedMethods = new ArrayList<>();
}
@Override
public void visit(int version, int access, @NonNull String name, @NonNull ClassInfo additionalInfo) {
ClassInfo subClassInfo = new ClassInfo();
subClassInfo.superName = name;
subClassInfo.signature = mockedTypeInfo == null ? additionalInfo.signature
: mockedTypeInfo.implementationSignature;
int subclassAccess = access & CLASS_ACCESS_MASK | Access.FINAL;
super.visit(version, subclassAccess, subclassName, subClassInfo);
superClassOfSuperClass = additionalInfo.superName;
superInterfaces = new HashSet<>();
String[] interfaces = additionalInfo.interfaces;
if (interfaces.length > 0) {
superInterfaces.addAll(asList(interfaces));
}
}
@Override
public final void visitInnerClass(@NonNull String name, @Nullable String outerName, @Nullable String innerName,
int access) {
}
@Override
@Nullable
public final FieldVisitor visitField(int access, @NonNull String name, @NonNull String desc,
@Nullable String signature, @Nullable Object value) {
return null;
}
@Override
@Nullable
public MethodVisitor visitMethod(int access, @NonNull String name, @NonNull String desc, @Nullable String signature,
@Nullable String[] exceptions) {
if (copyConstructors && "".equals(name)) {
if ((access & CONSTRUCTOR_ACCESS_MASK) != 0) {
generateConstructorDelegatingToSuper(desc, signature, exceptions);
}
} else {
// Inherits from super-class when non-abstract; otherwise, creates implementation for abstract method.
generateImplementationIfAbstractMethod(superClassName, access, name, desc, signature, exceptions);
}
return null;
}
private void generateConstructorDelegatingToSuper(@NonNull String desc, @Nullable String signature,
@Nullable String[] exceptions) {
mw = cw.visitMethod(Access.PUBLIC, "", desc, signature, exceptions);
mw.visitVarInsn(ALOAD, 0);
int varIndex = 1;
for (JavaType paramType : JavaType.getArgumentTypes(desc)) {
int loadOpcode = paramType.getLoadOpcode();
mw.visitVarInsn(loadOpcode, varIndex);
varIndex++;
}
mw.visitMethodInsn(INVOKESPECIAL, superClassName, "", desc, false);
generateEmptyImplementation();
}
private void generateImplementationIfAbstractMethod(String className, int access, @NonNull String name,
@NonNull String desc, @Nullable String signature, @Nullable String[] exceptions) {
if (!"".equals(name)) {
String methodNameAndDesc = name + desc;
if (!implementedMethods.contains(methodNameAndDesc)) {
if ((access & Access.ABSTRACT) != 0) {
generateMethodImplementation(className, access, name, desc, signature, exceptions);
}
implementedMethods.add(methodNameAndDesc);
}
}
}
protected void generateMethodImplementation(String className, int access, @NonNull String name,
@NonNull String desc, @Nullable String signature, @Nullable String[] exceptions) {
}
@Override
public void visitEnd() {
generateImplementationsForInheritedAbstractMethods(superClassOfSuperClass);
while (!superInterfaces.isEmpty()) {
String superInterface = superInterfaces.iterator().next();
generateImplementationsForAbstractMethods(superInterface, false);
superInterfaces.remove(superInterface);
}
}
private void generateImplementationsForInheritedAbstractMethods(@Nullable String superName) {
if (superName != null) {
generateImplementationsForAbstractMethods(superName, true);
}
}
private void generateImplementationsForAbstractMethods(@NonNull String typeName, boolean abstractClass) {
if (!"java/lang/Object".equals(typeName)) {
byte[] typeBytecode = ClassFile.getClassFile(typeName);
ClassMetadataReader cmr = new ClassMetadataReader(typeBytecode);
String[] interfaces = cmr.getInterfaces();
if (interfaces != null) {
superInterfaces.addAll(asList(interfaces));
}
for (MethodInfo method : cmr.getMethods()) {
if (abstractClass) {
generateImplementationIfAbstractMethod(typeName, method.accessFlags, method.name, method.desc, null,
null);
} else if (method.isAbstract()) {
generateImplementationForInterfaceMethodIfMissing(typeName, method);
}
}
if (abstractClass) {
String superClass = cmr.getSuperClass();
generateImplementationsForInheritedAbstractMethods(superClass);
}
}
}
private void generateImplementationForInterfaceMethodIfMissing(@NonNull String typeName,
@NonNull MethodInfo method) {
String name = method.name;
String desc = method.desc;
String methodNameAndDesc = name + desc;
if (!implementedMethods.contains(methodNameAndDesc)) {
if (!hasMethodImplementation(name, desc)) {
generateMethodImplementation(typeName, method.accessFlags, name, desc, null, null);
}
implementedMethods.add(methodNameAndDesc);
}
}
private boolean hasMethodImplementation(@NonNull String name, @NonNull String desc) {
Class>[] paramTypes = TypeDescriptor.getParameterTypes(desc);
try {
Method method = baseClass.getMethod(name, paramTypes);
return !method.getDeclaringClass().isInterface();
} catch (NoSuchMethodException ignore) {
return false;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy