/*
* Copyright (c) 2006 Rogério Liesenfeld
* This file is subject to the terms of the MIT license (see LICENSE.txt).
*/
package mockit.internal.classGeneration;
import java.lang.reflect.*;
import java.lang.reflect.Type;
import java.util.*;
import javax.annotation.*;
import static java.util.Arrays.*;
import mockit.external.asm.*;
import mockit.internal.*;
import mockit.internal.util.*;
import static mockit.external.asm.Opcodes.*;
public class BaseSubclassGenerator extends BaseClassModifier
{
private static final int CLASS_ACCESS_MASK = 0xFFFF - ACC_ABSTRACT;
private static final int CONSTRUCTOR_ACCESS_MASK = ACC_PUBLIC + ACC_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<String> implementedMethods;
@Nullable private String superClassOfSuperClass;
private Set<String> superInterfaces;
protected BaseSubclassGenerator(
@Nonnull Class<?> baseClass, @Nonnull ClassReader classReader,
@Nullable Type genericMockedType, @Nonnull String subclassName,
boolean copyConstructors)
{
super(classReader);
this.baseClass = baseClass;
this.subclassName = subclassName.replace('.', '/');
mockedTypeInfo = genericMockedType == null ? null : new MockedTypeInfo(genericMockedType);
this.copyConstructors = copyConstructors;
implementedMethods = new ArrayList<String>();
}
@Override
public void visit(
int version, int access, @Nonnull String name, @Nullable String signature, @Nullable String superName,
@Nullable String[] interfaces)
{
int subclassAccess = access & CLASS_ACCESS_MASK | ACC_FINAL;
String subclassSignature = mockedTypeInfo == null ? signature : mockedTypeInfo.implementationSignature;
super.visit(version, subclassAccess, subclassName, subclassSignature, name, null);
superClassOfSuperClass = superName;
superInterfaces = new HashSet<String>();
if (interfaces != null && interfaces.length > 0) {
superInterfaces.addAll(asList(interfaces));
}
}
@Override
public final void visitInnerClass(
@Nonnull String name, @Nullable String outerName, @Nullable String innerName, int access) {}
@Override
public final void visitOuterClass(@Nonnull String owner, @Nullable String name, @Nullable String desc) {}
@Override
public final void visitAttribute(Attribute attr) {}
@Override
public final void visitSource(@Nullable String source, @Nullable String debug) {}
@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 && "<init>".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(ACC_PUBLIC, "<init>", desc, signature, exceptions);
mw.visitVarInsn(ALOAD, 0);
int varIndex = 1;
for (mockit.external.asm.Type paramType : mockit.external.asm.Type.getArgumentTypes(desc)) {
int loadOpcode = getLoadOpcodeForParameterType(paramType.getSort());
mw.visitVarInsn(loadOpcode, varIndex);
varIndex++;
}
mw.visitMethodInsn(INVOKESPECIAL, superClassName, "<init>", desc, false);
generateEmptyImplementation();
}
private static int getLoadOpcodeForParameterType(int paramType)
{
if (paramType <= mockit.external.asm.Type.INT) {
return ILOAD;
}
switch (paramType) {
case mockit.external.asm.Type.FLOAT: return FLOAD;
case mockit.external.asm.Type.LONG: return LLOAD;
case mockit.external.asm.Type.DOUBLE: return DLOAD;
default: return ALOAD;
}
}
private void generateImplementationIfAbstractMethod(
String className, int access, @Nonnull String name, @Nonnull String desc, @Nullable String signature,
@Nullable String[] exceptions)
{
if (!"<init>".equals(name)) {
String methodNameAndDesc = name + desc;
if (!implementedMethods.contains(methodNameAndDesc)) {
if ((access & ACC_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();
generateImplementationsForInterfaceMethods(superInterface);
superInterfaces.remove(superInterface);
}
}
private void generateImplementationsForInheritedAbstractMethods(@Nullable String superName)
{
if (superName != null && !"java/lang/Object".equals(superName)) {
new MethodModifierForSuperclass(superName);
}
}
private void generateImplementationsForInterfaceMethods(String superName)
{
if (!"java/lang/Object".equals(superName)) {
new MethodModifierForImplementedInterface(superName);
}
}
private static class BaseMethodModifier extends ClassVisitor
{
@Nonnull final String typeName;
BaseMethodModifier(@Nonnull String typeName)
{
this.typeName = typeName;
ClassFile.visitClass(typeName, this);
}
@Nullable @Override
public final FieldVisitor visitField(
int access, @Nonnull String name, @Nonnull String desc, String signature, Object value) { return null; }
}
private final class MethodModifierForSuperclass extends BaseMethodModifier
{
String superClassName;
MethodModifierForSuperclass(@Nonnull String className) { super(className); }
@Override
public void visit(
int version, int access, @Nonnull String name, @Nullable String signature, @Nullable String superName,
@Nullable String[] interfaces)
{
superClassName = superName;
if (interfaces != null) {
superInterfaces.addAll(asList(interfaces));
}
}
@Nullable @Override
public MethodVisitor visitMethod(
int access, @Nonnull String name, @Nonnull String desc, String signature, String[] exceptions)
{
generateImplementationIfAbstractMethod(typeName, access, name, desc, signature, exceptions);
return null;
}
@Override
public void visitEnd()
{
generateImplementationsForInheritedAbstractMethods(superClassName);
}
}
private final class MethodModifierForImplementedInterface extends BaseMethodModifier
{
MethodModifierForImplementedInterface(String interfaceName) { super(interfaceName); }
@Override
public void visit(
int version, int access, @Nonnull String name, @Nullable String signature, @Nullable String superName,
@Nullable String[] interfaces)
{
assert interfaces != null;
superInterfaces.addAll(asList(interfaces));
}
@Nullable @Override
public MethodVisitor visitMethod(
int access, @Nonnull String name, @Nonnull String desc, String signature, String[] exceptions)
{
generateImplementationForInterfaceMethodIfMissing(access, name, desc, signature, exceptions);
return null;
}
private void generateImplementationForInterfaceMethodIfMissing(
int access, @Nonnull String name, @Nonnull String desc, String signature, String[] exceptions)
{
String methodNameAndDesc = name + desc;
if (!implementedMethods.contains(methodNameAndDesc)) {
if (!hasMethodImplementation(name, desc)) {
generateMethodImplementation(typeName, access, name, desc, signature, exceptions);
}
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;
}
}
}
}