BridgeMethod.java

package com.mackenziehigh.autumn.lang.compiler.utils;

import com.google.common.base.Preconditions;
import com.mackenziehigh.autumn.lang.compiler.compilers.ModuleCompiler;
import com.mackenziehigh.autumn.lang.compiler.typesystem.CustomDeclaredType;
import com.mackenziehigh.autumn.lang.compiler.typesystem.CustomMethod;
import com.mackenziehigh.autumn.lang.compiler.typesystem.design.IMethod;
import com.mackenziehigh.autumn.lang.compiler.typesystem.design.IReferenceType;
import com.mackenziehigh.autumn.lang.compiler.typesystem.design.IReturnType;
import com.mackenziehigh.autumn.lang.compiler.typesystem.design.IVariableType;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.InsnNode;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MethodNode;
import org.objectweb.asm.tree.TypeInsnNode;
import org.objectweb.asm.tree.VarInsnNode;

/**
 * An instance of this method simplifies the compilation of a bridge method.
 *
 * @author Mackenzie High
 */
public final class BridgeMethod
{
    /**
     * This is the type-system representation of the bridge method definition.
     */
    public final IMethod caller;

    /**
     * This is the type-system representation of the actual method definition.
     */
    public final IMethod callee;

    /**
     * Constructor.
     *
     * @param owner is the owner of the bridge definition.
     * @param returns is the return-type of the bridge method definition.
     * @param target is the target method definition.
     */
    public BridgeMethod(final CustomDeclaredType owner,
                        final IReturnType returns,
                        final IMethod target)
    {
        /**
         * Create the type-system representation of the bridge method.
         */
        CustomMethod builder = new CustomMethod(returns.getTypeFactory(), false);
        builder.setAnnotations(target.getAnnotations());
        builder.setModifiers(target.getModifiers());
        builder.setName(target.getName());
        builder.setOwner(owner);
        builder.setParameters(target.getParameters());
        builder.setReturnType(returns);
        builder.setThrowsClause(target.getThrowsClause());

        this.caller = builder;
        this.callee = target;
    }

    /**
     * Constructor.
     *
     * @param bridge is the bridge method.
     * @param target is the method that the bridge method invokes.
     */
    public BridgeMethod(final IMethod bridge,
                        final IMethod target)
    {
        Preconditions.checkNotNull(bridge);
        Preconditions.checkNotNull(target);

        this.caller = bridge;
        this.callee = target;
    }

    /**
     * This method creates the bytecode representation of the bridge method definition.
     *
     * @param module is the compiler of the enclosing module.
     * @return the generated bytecode.
     */
    public MethodNode compile(final ModuleCompiler module)
    {
        assert caller.getReturnType().isReferenceType();

        final MethodNode method = Utils.bytecodeOf(module, caller);

        // Remove the abstract and final modifiers.
        method.access = method.access & (~Opcodes.ACC_ABSTRACT);
        method.access = method.access & (~Opcodes.ACC_FINAL);

        // Add the bridge flag.
        method.access = method.access | Opcodes.ACC_BRIDGE;

        // Change the return-type.
        method.desc = caller.getDescriptor();

        // Load 'this'
        method.instructions.add(new VarInsnNode(Opcodes.ALOAD, 0));

        int address = 1;

        // Load the arguments.
        for (int i = 0; i < caller.getParameters().size(); i++)
        {
            final IVariableType argument_type = caller.getParameters().get(i).getType();

            final IVariableType parameter_type = callee.getParameters().get(i).getType();

            method.instructions.add(Utils.selectLoadVarInsn(argument_type, address));

            method.instructions.add(Utils.conditionalCast(argument_type, parameter_type));

            address += Utils.sizeof(argument_type);
        }

        // Invoke the non-bridge method.
        method.instructions.add(new MethodInsnNode(callee.getOwner().isInterfaceType() ? Opcodes.INVOKEINTERFACE : Opcodes.INVOKEVIRTUAL,
                                                   Utils.internalName(callee.getOwner()),
                                                   callee.getName(),
                                                   callee.getDescriptor()));

        // Cast the result produced by the non-bridge method to the appropriate type.
        method.instructions.add(new TypeInsnNode(Opcodes.CHECKCAST,
                                                 Utils.internalName((IReferenceType) caller.getReturnType())));

        // Return the result.
        method.instructions.add(new InsnNode(Opcodes.ARETURN));

        return method;
    }

    /**
     * This method creates the bytecode representation of an abstract bridge method definition.
     *
     * @param module is the compiler of the enclosing module.
     * @return the generated bytecode.
     */
    public MethodNode compileAbstract(final ModuleCompiler module)
    {
        assert caller.getReturnType().isReferenceType();

        final MethodNode method = Utils.bytecodeOf(module, caller);

        // Add the abstract and final modifiers.
        method.access = method.access | Opcodes.ACC_ABSTRACT;

        // Add the bridge flag.
        method.access = method.access | Opcodes.ACC_BRIDGE;

        // Change the return-type.
        method.desc = caller.getDescriptor();

        return method;
    }
}