All Downloads are FREE. Search and download functionalities are using the official Maven repository.

jetbrick.bean.asm.AsmBuilder Maven / Gradle / Ivy

/**
 * Copyright 2013-2016 Guoqiang Chen, Shanghai, China. All rights reserved.
 *
 *   Author: Guoqiang Chen
 *    Email: [email protected]
 *   WebURL: https://github.com/subchen
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package jetbrick.bean.asm;

import java.lang.reflect.Modifier;
import java.util.List;

import jetbrick.asm.ClassWriter;
import jetbrick.asm.Label;
import jetbrick.asm.MethodVisitor;
import jetbrick.asm.Type;
import jetbrick.bean.ConstructorInfo;
import jetbrick.bean.Executable;
import jetbrick.bean.FieldInfo;
import jetbrick.bean.KlassInfo;
import jetbrick.bean.MethodInfo;

import static jetbrick.asm.Opcodes.*;

final class AsmBuilder {
    private static final String SUN_MAGIC_ACCESSOR_KLASS = "sun/reflect/MagicAccessorImpl";
    private static final String FIELD_EXPECTED_CONSTRUCTOR_ARGUMENT_LENGTHS = "ctors";
    private static final String FIELD_EXPECTED_METHOD_ARGUMENT_LENGTHS = "methods";
    private static final String METHOD_CHECK_ARGUMENTS = "checkArguments";

    private final ClassWriter cw;
    private final String generatedKlassNameInternal;
    private final String delegateKlassNameInternal;

    public AsmBuilder(String generatedKlassName, String delegateKlassName, Class interfaceKlass) {
        generatedKlassNameInternal = generatedKlassName.replace('.', '/');
        delegateKlassNameInternal = delegateKlassName.replace('.', '/');

        cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
        String[] interfaces = new String[] { interfaceKlass.getName().replace('.', '/') };
        cw.visit(V1_1, ACC_PUBLIC + ACC_SUPER + ACC_FINAL, generatedKlassNameInternal, null, SUN_MAGIC_ACCESSOR_KLASS, interfaces);
    }

    public static byte[] create(String generatedKlassName, KlassInfo delegateKlass) {
        AsmBuilder builder = new AsmBuilder(generatedKlassName, delegateKlass.getName(), AsmAccessor.class);
        builder.insertArgumentsLengthField(delegateKlass.getDeclaredConstructors(), delegateKlass.getDeclaredMethods());
        builder.insertCheckArgumentsMethod();
        builder.insertConstructor();
        builder.insertNewInstance();
        builder.insertNewInstance(delegateKlass.getDeclaredConstructors());
        builder.insertInvoke(delegateKlass.getDeclaredMethods());
        builder.insertGetField(delegateKlass.getDeclaredFields());
        builder.insertSetField(delegateKlass.getDeclaredFields());
        return builder.asByteCode();
    }

    public void insertArgumentsLengthField(List constructors, List methods) {
        cw.visitField(ACC_PRIVATE + ACC_FINAL + ACC_STATIC, FIELD_EXPECTED_CONSTRUCTOR_ARGUMENT_LENGTHS, "[I", null, null).visitEnd();
        cw.visitField(ACC_PRIVATE + ACC_FINAL + ACC_STATIC, FIELD_EXPECTED_METHOD_ARGUMENT_LENGTHS, "[I", null, null).visitEnd();

        MethodVisitor mv = cw.visitMethod(ACC_STATIC, "", "()V", null, null);
        mv.visitCode();

        int size;

        // constructors
        size = constructors.size();
        pushIntValue(mv, size);
        mv.visitIntInsn(NEWARRAY, T_INT);
        for (int i = 0; i < size; i++) {
            mv.visitInsn(DUP);
            pushIntValue(mv, i);
            pushIntValue(mv, constructors.get(i).getParameterCount());
            mv.visitInsn(IASTORE);
        }
        mv.visitFieldInsn(PUTSTATIC, generatedKlassNameInternal, FIELD_EXPECTED_CONSTRUCTOR_ARGUMENT_LENGTHS, "[I");

        //  methods
        size = methods.size();
        pushIntValue(mv, size);
        mv.visitIntInsn(NEWARRAY, T_INT);
        for (int i = 0; i < size; i++) {
            mv.visitInsn(DUP);
            pushIntValue(mv, i);
            pushIntValue(mv, methods.get(i).getParameterCount());
            mv.visitInsn(IASTORE);
        }
        mv.visitFieldInsn(PUTSTATIC, generatedKlassNameInternal, FIELD_EXPECTED_METHOD_ARGUMENT_LENGTHS, "[I");

        mv.visitInsn(RETURN);
        mv.visitMaxs(size > 0 ? 4 : 1, 0);
        mv.visitEnd();
    }

    public void insertCheckArgumentsMethod() {
        // private static final void checkArguments(int[] argumentsLength, int offset, Object[] args);
        MethodVisitor mv = cw.visitMethod(ACC_PRIVATE + ACC_FINAL + ACC_STATIC, METHOD_CHECK_ARGUMENTS, "([II[Ljava/lang/Object;)V", null, null);
        mv.visitCode();

        Label labelStep2 = new Label();
        Label labelError2 = new Label();
        Label labelStep3 = new Label();
        Label labelSucc = new Label();

        // step1: if (args == null)
        mv.visitVarInsn(ALOAD, 2);
        mv.visitJumpInsn(IFNONNULL, labelStep2);

        throwIllegalArgumentException(mv, "arguments must be not null");

        // step2: if (offset < 0 || offset >= argumentsLength.length)
        mv.visitLabel(labelStep2);
        mv.visitVarInsn(ILOAD, 1);
        mv.visitJumpInsn(IFLT, labelError2);

        mv.visitVarInsn(ILOAD, 1);
        mv.visitVarInsn(ALOAD, 0);
        mv.visitInsn(ARRAYLENGTH);
        mv.visitJumpInsn(IF_ICMPLT, labelStep3);

        mv.visitLabel(labelError2);
        throwIllegalArgumentException(mv, "wrong offset of member");

        // step3: if (args.length != argumentsLength[which]) {
        mv.visitLabel(labelStep3);
        mv.visitVarInsn(ALOAD, 2);
        mv.visitInsn(ARRAYLENGTH);
        mv.visitVarInsn(ALOAD, 0);
        mv.visitVarInsn(ILOAD, 1);
        mv.visitInsn(IALOAD);
        mv.visitJumpInsn(IF_ICMPEQ, labelSucc);

        throwIllegalArgumentException(mv, "wrong number of arguments");

        //
        mv.visitLabel(labelSucc);
        mv.visitInsn(RETURN);

        mv.visitMaxs(4, 3);
        mv.visitEnd();
    }

    public void insertConstructor() {
        MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "", "()V", null, null);
        mv.visitCode();
        mv.visitVarInsn(ALOAD, 0);
        mv.visitMethodInsn(INVOKESPECIAL, SUN_MAGIC_ACCESSOR_KLASS, "", "()V", false);
        mv.visitInsn(RETURN);
        mv.visitMaxs(1, 1);
        mv.visitEnd();
    }

    public void insertNewInstance() {
        MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "newInstance", "()Ljava/lang/Object;", null, null);
        mv.visitCode();
        mv.visitTypeInsn(NEW, delegateKlassNameInternal);
        mv.visitInsn(DUP);
        mv.visitMethodInsn(INVOKESPECIAL, delegateKlassNameInternal, "", "()V", false);
        mv.visitInsn(ARETURN);
        mv.visitMaxs(2, 1);
        mv.visitEnd();
    }

    public void insertNewInstance(List constructors) {
        MethodVisitor mv = cw.visitMethod(ACC_PUBLIC + ACC_VARARGS, "newInstance", "(I[Ljava/lang/Object;)Ljava/lang/Object;", null, null);
        mv.visitCode();

        // checkArgument(expectedArgumentLengths, offset, arguments);
        mv.visitFieldInsn(GETSTATIC, generatedKlassNameInternal, FIELD_EXPECTED_CONSTRUCTOR_ARGUMENT_LENGTHS, "[I");
        mv.visitVarInsn(ILOAD, 1);
        mv.visitVarInsn(ALOAD, 2);
        mv.visitMethodInsn(INVOKESTATIC, generatedKlassNameInternal, METHOD_CHECK_ARGUMENTS, "([II[Ljava/lang/Object;)V", false);

        int n = constructors.size();
        if (n != 0) {
            mv.visitVarInsn(ILOAD, 1);
            Label[] labels = new Label[n];
            for (int i = 0; i < n; i++) {
                labels[i] = new Label();
            }
            Label defaultLabel = new Label();
            mv.visitTableSwitchInsn(0, labels.length - 1, defaultLabel, labels);

            StringBuilder buffer = new StringBuilder(128);
            for (int i = 0; i < n; i++) {
                mv.visitLabel(labels[i]);
                mv.visitFrame(F_SAME, 0, null, 0, null);

                mv.visitTypeInsn(NEW, delegateKlassNameInternal);
                mv.visitInsn(DUP);

                buffer.setLength(0);
                buffer.append('(');
                Class[] paramTypes = constructors.get(i).getParameterTypes();
                for (int paramIndex = 0; paramIndex < paramTypes.length; paramIndex++) {
                    mv.visitVarInsn(ALOAD, 2);
                    pushIntValue(mv, paramIndex);
                    mv.visitInsn(AALOAD);

                    Type type = Type.getType(paramTypes[paramIndex]);
                    insertUnbox(mv, type);
                    buffer.append(type.getDescriptor());
                }
                buffer.append(")V");
                mv.visitMethodInsn(INVOKESPECIAL, delegateKlassNameInternal, "", buffer.toString(), false);
                mv.visitInsn(ARETURN);
            }
            mv.visitLabel(defaultLabel);
            mv.visitFrame(F_SAME, 0, null, 0, null);
        }
        throwIllegalArgumentException(mv, "wrong offset of constructor");

        mv.visitMaxs(0, 0);
        mv.visitEnd();
    }

    public void insertInvoke(List methods) {
        MethodVisitor mv = cw.visitMethod(ACC_PUBLIC + ACC_VARARGS, "invoke", "(Ljava/lang/Object;I[Ljava/lang/Object;)Ljava/lang/Object;", null, null);
        mv.visitCode();

        // checkArgument(expectedArgumentLengths, offset, arguments);
        mv.visitFieldInsn(GETSTATIC, generatedKlassNameInternal, FIELD_EXPECTED_METHOD_ARGUMENT_LENGTHS, "[I");
        mv.visitVarInsn(ILOAD, 2);
        mv.visitVarInsn(ALOAD, 3);
        mv.visitMethodInsn(INVOKESTATIC, generatedKlassNameInternal, METHOD_CHECK_ARGUMENTS, "([II[Ljava/lang/Object;)V", false);

        int n = methods.size();
        if (n != 0) {
            mv.visitVarInsn(ILOAD, 2);
            Label[] labels = new Label[n];
            for (int i = 0; i < n; i++) {
                labels[i] = new Label();
            }
            Label defaultLabel = new Label();
            mv.visitTableSwitchInsn(0, labels.length - 1, defaultLabel, labels);

            StringBuilder buffer = new StringBuilder(128);
            for (int i = 0; i < n; i++) {
                mv.visitLabel(labels[i]);
                mv.visitFrame(F_SAME, 0, null, 0, null);

                MethodInfo method = methods.get(i);
                boolean isInterface = method.getDeclaringKlass().isInterface();
                boolean isStatic = method.isStatic();

                if (!isStatic) {
                    mv.visitVarInsn(ALOAD, 1);
                    mv.visitTypeInsn(CHECKCAST, delegateKlassNameInternal);
                }

                buffer.setLength(0);
                buffer.append('(');

                String methodName = method.getName();
                Class[] paramTypes = method.getParameterTypes();
                Class returnType = method.getReturnType();
                for (int paramIndex = 0; paramIndex < paramTypes.length; paramIndex++) {
                    mv.visitVarInsn(ALOAD, 3);
                    pushIntValue(mv, paramIndex);
                    mv.visitInsn(AALOAD);
                    Type type = Type.getType(paramTypes[paramIndex]);
                    insertUnbox(mv, type);
                    buffer.append(type.getDescriptor());
                }
                buffer.append(')');
                buffer.append(Type.getDescriptor(returnType));

                int opcode;
                if (isInterface) {
                    opcode = INVOKEINTERFACE;
                } else if (isStatic) {
                    opcode = INVOKESTATIC;
                } else if (method.isPrivate() || method.isFinal()) {
                    opcode = INVOKESPECIAL;
                } else {
                    opcode = INVOKEVIRTUAL;
                }
                mv.visitMethodInsn(opcode, delegateKlassNameInternal, methodName, buffer.toString(), isInterface);

                insertBox(mv, Type.getType(returnType));
                mv.visitInsn(ARETURN);
            }
            mv.visitLabel(defaultLabel);
            mv.visitFrame(F_SAME, 0, null, 0, null);
        }
        throwIllegalArgumentException(mv, "wrong offset of method");

        mv.visitMaxs(0, 0);
        mv.visitEnd();
    }

    public void insertGetField(List fields) {
        MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "getField", "(Ljava/lang/Object;I)Ljava/lang/Object;", null, null);
        mv.visitCode();

        int n = fields.size();
        if (n != 0) {
            mv.visitVarInsn(ILOAD, 2);
            Label[] labels = new Label[n];
            for (int i = 0; i < n; i++) {
                labels[i] = new Label();
            }
            Label defaultLabel = new Label();
            mv.visitTableSwitchInsn(0, labels.length - 1, defaultLabel, labels);

            for (int i = 0; i < n; i++) {
                mv.visitLabel(labels[i]);
                mv.visitFrame(F_SAME, 0, null, 0, null);

                FieldInfo field = fields.get(i);
                Type type = Type.getType(field.getType());
                if (Modifier.isStatic(field.getModifiers())) {
                    mv.visitFieldInsn(GETSTATIC, delegateKlassNameInternal, field.getName(), type.getDescriptor());
                } else {
                    mv.visitVarInsn(ALOAD, 1);
                    mv.visitTypeInsn(CHECKCAST, delegateKlassNameInternal);
                    mv.visitFieldInsn(GETFIELD, delegateKlassNameInternal, field.getName(), type.getDescriptor());
                }
                insertBox(mv, type);
                mv.visitInsn(ARETURN);
            }
            mv.visitLabel(defaultLabel);
            mv.visitFrame(F_SAME, 0, null, 0, null);
        }
        throwIllegalArgumentException(mv, "wrong offset of field");

        int maxStack = fields.isEmpty() ? 5 : 6;
        mv.visitMaxs(maxStack, 3);
        mv.visitEnd();
    }

    public void insertSetField(List fields) {
        MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "setField", "(Ljava/lang/Object;ILjava/lang/Object;)V", null, null);
        mv.visitCode();

        int n = fields.size();
        if (n != 0) {
            mv.visitVarInsn(ILOAD, 2);
            Label[] labels = new Label[n];
            for (int i = 0; i < n; i++) {
                labels[i] = new Label();
            }
            Label defaultLabel = new Label();
            mv.visitTableSwitchInsn(0, labels.length - 1, defaultLabel, labels);

            for (int i = 0; i < n; i++) {
                mv.visitLabel(labels[i]);
                mv.visitFrame(F_SAME, 0, null, 0, null);

                FieldInfo field = fields.get(i);
                Type type = Type.getType(field.getType());
                boolean isStatic = Modifier.isStatic(field.getModifiers());
                if (!isStatic) {
                    mv.visitVarInsn(ALOAD, 1);
                    mv.visitTypeInsn(CHECKCAST, delegateKlassNameInternal);
                }
                mv.visitVarInsn(ALOAD, 3);
                insertUnbox(mv, type);
                mv.visitFieldInsn(isStatic ? PUTSTATIC : PUTFIELD, delegateKlassNameInternal, field.getName(), type.getDescriptor());
                mv.visitInsn(RETURN);
            }
            mv.visitLabel(defaultLabel);
            mv.visitFrame(F_SAME, 0, null, 0, null);
        }
        throwIllegalArgumentException(mv, "wrong offset of field");

        int maxStack = fields.isEmpty() ? 5 : 6;
        mv.visitMaxs(maxStack, 4);
        mv.visitEnd();
    }

    private static void throwIllegalArgumentException(MethodVisitor mv, String message) {
        mv.visitTypeInsn(NEW, "java/lang/IllegalArgumentException");
        mv.visitInsn(DUP);
        mv.visitLdcInsn(message);
        mv.visitMethodInsn(INVOKESPECIAL, "java/lang/IllegalArgumentException", "", "(Ljava/lang/String;)V", false);
        mv.visitInsn(ATHROW);
    }

    private static void insertBox(MethodVisitor mv, Type type) {
        switch (type.getSort()) {
        case Type.VOID:
            mv.visitInsn(ACONST_NULL);
            break;
        case Type.BOOLEAN:
            mv.visitMethodInsn(INVOKESTATIC, "java/lang/Boolean", "valueOf", "(Z)Ljava/lang/Boolean;", false);
            break;
        case Type.BYTE:
            mv.visitMethodInsn(INVOKESTATIC, "java/lang/Byte", "valueOf", "(B)Ljava/lang/Byte;", false);
            break;
        case Type.CHAR:
            mv.visitMethodInsn(INVOKESTATIC, "java/lang/Character", "valueOf", "(C)Ljava/lang/Character;", false);
            break;
        case Type.SHORT:
            mv.visitMethodInsn(INVOKESTATIC, "java/lang/Short", "valueOf", "(S)Ljava/lang/Short;", false);
            break;
        case Type.INT:
            mv.visitMethodInsn(INVOKESTATIC, "java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;", false);
            break;
        case Type.FLOAT:
            mv.visitMethodInsn(INVOKESTATIC, "java/lang/Float", "valueOf", "(F)Ljava/lang/Float;", false);
            break;
        case Type.LONG:
            mv.visitMethodInsn(INVOKESTATIC, "java/lang/Long", "valueOf", "(J)Ljava/lang/Long;", false);
            break;
        case Type.DOUBLE:
            mv.visitMethodInsn(INVOKESTATIC, "java/lang/Double", "valueOf", "(D)Ljava/lang/Double;", false);
            break;
        }
    }

    private static void insertUnbox(MethodVisitor mv, Type type) {
        switch (type.getSort()) {
        case Type.BOOLEAN:
            mv.visitTypeInsn(CHECKCAST, "java/lang/Boolean");
            mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Boolean", "booleanValue", "()Z", false);
            break;
        case Type.BYTE:
            mv.visitTypeInsn(CHECKCAST, "java/lang/Number");
            mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Number", "byteValue", "()B", false);
            break;
        case Type.CHAR:
            mv.visitTypeInsn(CHECKCAST, "java/lang/Character");
            mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Character", "charValue", "()C", false);
            break;
        case Type.SHORT:
            mv.visitTypeInsn(CHECKCAST, "java/lang/Number");
            mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Number", "shortValue", "()S", false);
            break;
        case Type.INT:
            mv.visitTypeInsn(CHECKCAST, "java/lang/Number");
            mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Number", "intValue", "()I", false);
            break;
        case Type.FLOAT:
            mv.visitTypeInsn(CHECKCAST, "java/lang/Number");
            mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Number", "floatValue", "()F", false);
            break;
        case Type.LONG:
            mv.visitTypeInsn(CHECKCAST, "java/lang/Number");
            mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Number", "longValue", "()J", false);
            break;
        case Type.DOUBLE:
            mv.visitTypeInsn(CHECKCAST, "java/lang/Number");
            mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Number", "doubleValue", "()D", false);
            break;
        case Type.ARRAY:
            mv.visitTypeInsn(CHECKCAST, type.getDescriptor());
            break;
        case Type.OBJECT:
            String internalName = type.getInternalName();
            if (!"java/lang/Object".equals(internalName)) {
                mv.visitTypeInsn(CHECKCAST, internalName);
            }
            break;
        }
    }

    public byte[] asByteCode() {
        cw.visitEnd();
        return cw.toByteArray();
    }

    private void pushIntValue(MethodVisitor mv, int value) {
        switch (value) {
        case 0:
            mv.visitInsn(ICONST_0);
            return;
        case 1:
            mv.visitInsn(ICONST_1);
            return;
        case 2:
            mv.visitInsn(ICONST_2);
            return;
        case 3:
            mv.visitInsn(ICONST_3);
            return;
        case 4:
            mv.visitInsn(ICONST_4);
            return;
        case 5:
            mv.visitInsn(ICONST_5);
            return;
        }
        if (value >= Byte.MIN_VALUE && value <= Byte.MAX_VALUE) {
            mv.visitIntInsn(BIPUSH, value);
        } else if (value >= Short.MIN_VALUE && value <= Short.MAX_VALUE) {
            mv.visitIntInsn(SIPUSH, value);
        } else {
            mv.visitLdcInsn(Integer.valueOf(value));
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy