org.jadira.reflection.access.asm.AsmClassAccess.java Source code

Java tutorial

Introduction

Here is the source code for org.jadira.reflection.access.asm.AsmClassAccess.java

Source

/*
 *  Copyright 2013 Christopher Pheby
 *
 *  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 org.jadira.reflection.access.asm;

import static org.objectweb.asm.Opcodes.AALOAD;
import static org.objectweb.asm.Opcodes.ACC_PUBLIC;
import static org.objectweb.asm.Opcodes.ACC_SUPER;
import static org.objectweb.asm.Opcodes.ACC_VARARGS;
import static org.objectweb.asm.Opcodes.ACONST_NULL;
import static org.objectweb.asm.Opcodes.ALOAD;
import static org.objectweb.asm.Opcodes.ARETURN;
import static org.objectweb.asm.Opcodes.ASTORE;
import static org.objectweb.asm.Opcodes.ATHROW;
import static org.objectweb.asm.Opcodes.BIPUSH;
import static org.objectweb.asm.Opcodes.CHECKCAST;
import static org.objectweb.asm.Opcodes.DLOAD;
import static org.objectweb.asm.Opcodes.DRETURN;
import static org.objectweb.asm.Opcodes.DUP;
import static org.objectweb.asm.Opcodes.FLOAD;
import static org.objectweb.asm.Opcodes.FRETURN;
import static org.objectweb.asm.Opcodes.F_APPEND;
import static org.objectweb.asm.Opcodes.F_SAME;
import static org.objectweb.asm.Opcodes.GETFIELD;
import static org.objectweb.asm.Opcodes.ILOAD;
import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
import static org.objectweb.asm.Opcodes.INVOKESTATIC;
import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL;
import static org.objectweb.asm.Opcodes.IRETURN;
import static org.objectweb.asm.Opcodes.ISTORE;
import static org.objectweb.asm.Opcodes.LLOAD;
import static org.objectweb.asm.Opcodes.LRETURN;
import static org.objectweb.asm.Opcodes.NEW;
import static org.objectweb.asm.Opcodes.POP;
import static org.objectweb.asm.Opcodes.PUTFIELD;
import static org.objectweb.asm.Opcodes.RETURN;
import static org.objectweb.asm.Opcodes.V1_7;
import static org.objectweb.asm.Type.BOOLEAN;
import static org.objectweb.asm.Type.BYTE;
import static org.objectweb.asm.Type.CHAR;
import static org.objectweb.asm.Type.DOUBLE;
import static org.objectweb.asm.Type.FLOAT;
import static org.objectweb.asm.Type.INT;
import static org.objectweb.asm.Type.LONG;
import static org.objectweb.asm.Type.SHORT;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.concurrent.ConcurrentHashMap;

import org.jadira.reflection.access.AbstractClassAccess;
import org.jadira.reflection.access.api.ClassAccess;
import org.jadira.reflection.access.api.FieldAccess;
import org.jadira.reflection.access.api.MethodAccess;
import org.jadira.reflection.access.classloader.AccessClassLoader;
import org.jadira.reflection.access.portable.PortableFieldAccess;
import org.jadira.reflection.core.misc.ClassUtils;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Type;

/**
 * ClassAccess implementation which uses ASM to generate accessors
 * @param <C> The Class to be accessed
 */
public abstract class AsmClassAccess<C> extends AbstractClassAccess<C> implements ClassAccess<C> {

    private static final ConcurrentHashMap<Class<?>, AsmClassAccess<?>> CLASS_ACCESSES = new ConcurrentHashMap<Class<?>, AsmClassAccess<?>>();

    private static final String CLASS_ACCESS_NM = ClassAccess.class.getName().replace('.', '/');

    private static final String ASM_CLASS_ACCESS_NM = AsmClassAccess.class.getName().replace('.', '/');

    private boolean isNonStaticMemberClass;

    /**
     * Constructor, intended for use by generated subclasses
     * @param clazz The Class to be accessed
     */
    protected AsmClassAccess(Class<C> clazz) {
        super(clazz);
    }

    /**
     * Indicates if the class being accessed is a non-static member class
     * @return True if the class is a non-static member class
     */
    public boolean isNonStaticMemberClass() {
        return isNonStaticMemberClass;
    }

    /**
     * Get a new instance that can access the given Class. If the ClassAccess for this class
     * has not been obtained before, then the specific AsmClassAccess is created by generating
     * a specialised subclass of this class and returning it. 
     * @param clazz Class to be accessed
     * @param <C> The type of class
     * @return New AsmClassAccess instance
     */
    public static <C> AsmClassAccess<C> get(Class<C> clazz) {

        @SuppressWarnings("unchecked")
        AsmClassAccess<C> access = (AsmClassAccess<C>) CLASS_ACCESSES.get(clazz);
        if (access != null) {
            return access;
        }

        Class<?> enclosingType = clazz.getEnclosingClass();

        final boolean isNonStaticMemberClass = determineNonStaticMemberClass(clazz, enclosingType);

        String clazzName = clazz.getName();

        Field[] fields = ClassUtils.collectInstanceFields(clazz, false, false, true);
        Method[] methods = ClassUtils.collectMethods(clazz);

        String accessClassName = constructAccessClassName(clazzName);

        Class<?> accessClass = null;

        AccessClassLoader loader = AccessClassLoader.get(clazz);
        synchronized (loader) {
            try {
                accessClass = loader.loadClass(accessClassName);
            } catch (ClassNotFoundException ignored) {

                String accessClassNm = accessClassName.replace('.', '/');
                String clazzNm = clazzName.replace('.', '/');
                String enclosingClassNm = determineEnclosingClassNm(clazz, enclosingType, isNonStaticMemberClass);

                String signatureString = "L" + ASM_CLASS_ACCESS_NM + "<L" + clazzNm + ";>;L" + CLASS_ACCESS_NM
                        + "<L" + clazzNm + ";>;";

                ClassWriter cw = new ClassWriter(0);

                //            TraceClassVisitor tcv = new TraceClassVisitor(cv, new PrintWriter(System.err));
                //            CheckClassAdapter cw = new CheckClassAdapter(tcv);

                cw.visit(V1_7, ACC_PUBLIC + ACC_SUPER, accessClassNm, signatureString, ASM_CLASS_ACCESS_NM, null);

                enhanceForConstructor(cw, accessClassNm, clazzNm);

                if (isNonStaticMemberClass) {
                    enhanceForNewInstanceInner(cw, clazzNm, enclosingClassNm);
                } else {
                    enhanceForNewInstance(cw, clazzNm);
                }

                enhanceForGetValueObject(cw, accessClassNm, clazzNm, fields);
                enhanceForPutValueObject(cw, accessClassNm, clazzNm, fields);
                enhanceForGetValuePrimitive(cw, accessClassNm, clazzNm, fields, Type.BOOLEAN_TYPE);
                enhanceForPutValuePrimitive(cw, accessClassNm, clazzNm, fields, Type.BOOLEAN_TYPE);
                enhanceForGetValuePrimitive(cw, accessClassNm, clazzNm, fields, Type.BYTE_TYPE);
                enhanceForPutValuePrimitive(cw, accessClassNm, clazzNm, fields, Type.BYTE_TYPE);
                enhanceForGetValuePrimitive(cw, accessClassNm, clazzNm, fields, Type.SHORT_TYPE);
                enhanceForPutValuePrimitive(cw, accessClassNm, clazzNm, fields, Type.SHORT_TYPE);
                enhanceForGetValuePrimitive(cw, accessClassNm, clazzNm, fields, Type.INT_TYPE);
                enhanceForPutValuePrimitive(cw, accessClassNm, clazzNm, fields, Type.INT_TYPE);
                enhanceForGetValuePrimitive(cw, accessClassNm, clazzNm, fields, Type.LONG_TYPE);
                enhanceForPutValuePrimitive(cw, accessClassNm, clazzNm, fields, Type.LONG_TYPE);
                enhanceForGetValuePrimitive(cw, accessClassNm, clazzNm, fields, Type.DOUBLE_TYPE);
                enhanceForPutValuePrimitive(cw, accessClassNm, clazzNm, fields, Type.DOUBLE_TYPE);
                enhanceForGetValuePrimitive(cw, accessClassNm, clazzNm, fields, Type.FLOAT_TYPE);
                enhanceForPutValuePrimitive(cw, accessClassNm, clazzNm, fields, Type.FLOAT_TYPE);
                enhanceForGetValuePrimitive(cw, accessClassNm, clazzNm, fields, Type.CHAR_TYPE);
                enhanceForPutValuePrimitive(cw, accessClassNm, clazzNm, fields, Type.CHAR_TYPE);
                enhanceForInvokeMethod(cw, accessClassNm, clazzNm, methods);

                cw.visitEnd();

                loader.registerClass(accessClassName, cw.toByteArray());

                try {
                    accessClass = loader.findClass(accessClassName);
                } catch (ClassNotFoundException e) {
                    throw new IllegalStateException("AccessClass unexpectedly could not be found", e);
                }
            }
        }

        try {
            @SuppressWarnings("unchecked")
            Constructor<AsmClassAccess<C>> c = (Constructor<AsmClassAccess<C>>) accessClass
                    .getConstructor(new Class[] { Class.class });

            access = c.newInstance(clazz);
            access.isNonStaticMemberClass = isNonStaticMemberClass;

            CLASS_ACCESSES.putIfAbsent(clazz, access);

            return access;
        } catch (Exception ex) {
            throw new RuntimeException("Error constructing constructor access class: " + accessClassName + "{ "
                    + ex.getMessage() + " }", ex);
        }
    }

    private static Label[] constructLabels(Field[] fields) {

        Label[] labels = new Label[fields.length];
        for (int i = 0, n = labels.length; i < n; i++) {
            labels[i] = new Label();
        }
        return labels;
    }

    private static Label[] constructLabels(Method[] methods) {

        Label[] labels = new Label[methods.length];
        for (int i = 0, n = labels.length; i < n; i++) {
            labels[i] = new Label();
        }
        return labels;
    }

    private static <C> String determineEnclosingClassNm(Class<C> clazz, Class<?> enclosingType,
            final boolean isNonStaticMemberClass) {

        final String enclosingClassNm;

        if (!isNonStaticMemberClass) {

            enclosingClassNm = null;
        } else {

            enclosingClassNm = enclosingType.getName().replace('.', '/');
        }
        return enclosingClassNm;
    }

    private static boolean determineNonStaticMemberClass(Class<?> clazz, Class<?> enclosingType) {
        final boolean isNonStaticMemberClass;
        if (enclosingType != null && clazz.isMemberClass() && !Modifier.isStatic(clazz.getModifiers())) {
            isNonStaticMemberClass = true;
        } else {
            isNonStaticMemberClass = false;
        }
        ;
        return isNonStaticMemberClass;
    }

    private static String constructAccessClassName(String clazzName) {

        String accessClassName = clazzName + AsmClassAccess.class.getSimpleName();
        if (accessClassName.startsWith("java.")) {
            accessClassName = AsmClassAccess.class.getSimpleName().toLowerCase() + accessClassName;
        }

        return accessClassName;
    }

    private static void enhanceForConstructor(ClassVisitor cw, String accessClassNm, String clazzNm) {
        MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "<init>", "(Ljava/lang/Class;)V", "(L" + clazzNm + ";)V",
                null);
        mv.visitCode();
        mv.visitVarInsn(ALOAD, 0);
        mv.visitVarInsn(ALOAD, 1);
        mv.visitMethodInsn(INVOKESPECIAL, ASM_CLASS_ACCESS_NM, "<init>", "(Ljava/lang/Class;)V");
        mv.visitInsn(RETURN);
        mv.visitMaxs(2, 2);
        mv.visitEnd();
    }

    private static void enhanceForNewInstance(ClassVisitor cw, String classNm) {
        MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "newInstance", "()Ljava/lang/Object;", null, null);

        mv.visitCode();
        mv.visitTypeInsn(NEW, classNm);
        mv.visitInsn(DUP);
        mv.visitMethodInsn(INVOKESPECIAL, classNm, "<init>", "()V");
        mv.visitInsn(ARETURN);
        mv.visitMaxs(2, 1);
        mv.visitEnd();
    }

    private static void enhanceForNewInstanceInner(ClassVisitor cw, String classNm, String enclosingClassNm) {
        MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "newInstance", "()LLjava/lang/Object;", null, null);
        mv.visitCode();
        mv.visitTypeInsn(NEW, classNm);
        mv.visitInsn(DUP);
        mv.visitTypeInsn(NEW, enclosingClassNm);
        mv.visitInsn(DUP);
        mv.visitMethodInsn(INVOKESPECIAL, enclosingClassNm, "<init>", "()V");
        mv.visitInsn(DUP);
        mv.visitMethodInsn(INVOKEVIRTUAL, classNm, "getClass", "()Ljava/lang/Class;");
        mv.visitInsn(POP);
        mv.visitMethodInsn(INVOKESPECIAL, classNm, "<init>", "(L" + enclosingClassNm + ";)V");
        mv.visitInsn(ARETURN);
        mv.visitMaxs(4, 1);
        mv.visitEnd();
    }

    private static void enhanceForGetValueObject(ClassVisitor cw, String accessClassNm, String clazzNm,
            Field[] fields) {

        MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "getValue",
                "(Ljava/lang/Object;Ljava/lang/String;)Ljava/lang/Object;", null, null);

        mv.visitCode();
        mv.visitVarInsn(ALOAD, 0);
        mv.visitFieldInsn(GETFIELD, accessClassNm, "fieldNames", "[Ljava/lang/String;");
        mv.visitVarInsn(ALOAD, 2);
        mv.visitMethodInsn(INVOKESTATIC, "java/util/Arrays", "binarySearch",
                "([Ljava/lang/Object;Ljava/lang/Object;)I");
        mv.visitVarInsn(ISTORE, 3);
        mv.visitVarInsn(ILOAD, 3);

        final int maxStack;

        if (fields.length > 0) {
            maxStack = 5;
            Label[] labels = constructLabels(fields);

            Label defaultLabel = new Label();
            mv.visitTableSwitchInsn(0, labels.length - 1, defaultLabel, labels);

            for (int i = 0, n = labels.length; i < n; i++) {
                Field field = fields[i];
                mv.visitLabel(labels[i]);
                mv.visitFrame(F_SAME, 0, null, 0, null);
                mv.visitVarInsn(ALOAD, 1);
                mv.visitTypeInsn(CHECKCAST, clazzNm);
                mv.visitFieldInsn(GETFIELD, clazzNm, field.getName(), Type.getDescriptor(field.getType()));

                Type fieldType = Type.getType(field.getType());
                switch (fieldType.getSort()) {
                case Type.BOOLEAN:
                    mv.visitMethodInsn(INVOKESTATIC, "java/lang/Boolean", "valueOf", "(Z)Ljava/lang/Boolean;");
                    break;
                case Type.BYTE:
                    mv.visitMethodInsn(INVOKESTATIC, "java/lang/Byte", "valueOf", "(B)Ljava/lang/Byte;");
                    break;
                case Type.CHAR:
                    mv.visitMethodInsn(INVOKESTATIC, "java/lang/Character", "valueOf", "(C)Ljava/lang/Character;");
                    break;
                case Type.SHORT:
                    mv.visitMethodInsn(INVOKESTATIC, "java/lang/Short", "valueOf", "(S)Ljava/lang/Short;");
                    break;
                case Type.INT:
                    mv.visitMethodInsn(INVOKESTATIC, "java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;");
                    break;
                case Type.FLOAT:
                    mv.visitMethodInsn(INVOKESTATIC, "java/lang/Float", "valueOf", "(F)Ljava/lang/Float;");
                    break;
                case Type.LONG:
                    mv.visitMethodInsn(INVOKESTATIC, "java/lang/Long", "valueOf", "(J)Ljava/lang/Long;");
                    break;
                case Type.DOUBLE:
                    mv.visitMethodInsn(INVOKESTATIC, "java/lang/Double", "valueOf", "(D)Ljava/lang/Double;");
                    break;
                }

                mv.visitInsn(ARETURN);
            }

            mv.visitLabel(defaultLabel);
            mv.visitFrame(F_SAME, 0, null, 0, null);
        } else {
            maxStack = 6;
        }
        enhanceForThrowingException(mv, IllegalArgumentException.class, "Field was not found", "Ljava/lang/Object;",
                ALOAD, 2);
        mv.visitMaxs(maxStack, 4);
        mv.visitEnd();
    }

    private static void enhanceForPutValueObject(ClassVisitor cw, String accessClassNm, String clazzNm,
            Field[] fields) {

        int maxStack = 6;
        MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "putValue",
                "(Ljava/lang/Object;Ljava/lang/String;Ljava/lang/Object;)V", null, null);

        mv.visitCode();

        mv.visitVarInsn(ALOAD, 0);
        mv.visitFieldInsn(GETFIELD, accessClassNm, "fieldNames", "[Ljava/lang/String;");
        mv.visitVarInsn(ALOAD, 2);
        mv.visitMethodInsn(INVOKESTATIC, "java/util/Arrays", "binarySearch",
                "([Ljava/lang/Object;Ljava/lang/Object;)I");
        mv.visitVarInsn(ISTORE, 4);
        mv.visitVarInsn(ILOAD, 4);

        if (fields.length > 0) {
            maxStack = 5;
            Label[] labels = constructLabels(fields);

            Label defaultLabel = new Label();
            mv.visitTableSwitchInsn(0, labels.length - 1, defaultLabel, labels);

            for (int i = 0, n = labels.length; i < n; i++) {
                Field field = fields[i];

                mv.visitLabel(labels[i]);
                mv.visitFrame(F_SAME, 0, null, 0, null);
                mv.visitVarInsn(ALOAD, 1);
                mv.visitTypeInsn(CHECKCAST, clazzNm);

                mv.visitVarInsn(ALOAD, 3);

                Type fieldType = Type.getType(field.getType());
                switch (fieldType.getSort()) {
                case Type.BOOLEAN:
                    mv.visitTypeInsn(CHECKCAST, "java/lang/Boolean");
                    mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Boolean", "booleanValue", "()Z");
                    break;
                case Type.BYTE:
                    mv.visitTypeInsn(CHECKCAST, "java/lang/Byte");
                    mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Byte", "byteValue", "()B");
                    break;
                case Type.CHAR:
                    mv.visitTypeInsn(CHECKCAST, "java/lang/Character");
                    mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Character", "charValue", "()C");
                    break;
                case Type.SHORT:
                    mv.visitTypeInsn(CHECKCAST, "java/lang/Short");
                    mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Short", "shortValue", "()S");
                    break;
                case Type.INT:
                    mv.visitTypeInsn(CHECKCAST, "java/lang/Integer");
                    mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Integer", "intValue", "()I");
                    break;
                case Type.FLOAT:
                    mv.visitTypeInsn(CHECKCAST, "java/lang/Float");
                    mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Float", "floatValue", "()F");
                    break;
                case Type.LONG:
                    mv.visitTypeInsn(CHECKCAST, "java/lang/Long");
                    mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Long", "longValue", "()J");
                    break;
                case Type.DOUBLE:
                    mv.visitTypeInsn(CHECKCAST, "java/lang/Double");
                    mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Double", "doubleValue", "()D");
                    break;
                case Type.ARRAY:
                    mv.visitTypeInsn(CHECKCAST, fieldType.getDescriptor());
                    break;
                case Type.OBJECT:
                    mv.visitTypeInsn(CHECKCAST, fieldType.getInternalName());
                    break;
                }

                mv.visitFieldInsn(PUTFIELD, clazzNm, field.getName(), fieldType.getDescriptor());
                mv.visitInsn(RETURN);
            }

            mv.visitLabel(defaultLabel);
            mv.visitFrame(F_SAME, 0, null, 0, null);
        } else {
            maxStack = 6;
        }
        enhanceForThrowingException(mv, IllegalArgumentException.class, "Field was not found", "Ljava/lang/Object;",
                ALOAD, 2);
        mv.visitMaxs(maxStack, 5);
        mv.visitEnd();
    }

    private static void enhanceForPutValuePrimitive(ClassVisitor cw, String accessClassNm, String clazzNm,
            Field[] fields, Type type) {

        final String methodName;
        final String typeNm = type.getDescriptor();
        final int instruction;

        switch (type.getSort()) {
        case BOOLEAN:
            methodName = "putBooleanValue";
            instruction = ILOAD;
            break;
        case BYTE:
            methodName = "putByteValue";
            instruction = ILOAD;
            break;
        case CHAR:
            methodName = "putCharValue";
            instruction = ILOAD;
            break;
        case SHORT:
            methodName = "putShortValue";
            instruction = ILOAD;
            break;
        case INT:
            methodName = "putIntValue";
            instruction = ILOAD;
            break;
        case FLOAT:
            methodName = "putFloatValue";
            instruction = FLOAD;
            break;
        case LONG:
            methodName = "putLongValue";
            instruction = LLOAD;
            break;
        case DOUBLE:
            methodName = "putDoubleValue";
            instruction = DLOAD;
            break;
        default:
            methodName = "put" + type.getInternalName().lastIndexOf('/') + "Value";
            instruction = ALOAD;
            break;
        }

        int offset = (instruction == LLOAD || instruction == DLOAD) ? 1 : 0;

        int maxStack = 6;
        MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, methodName,
                "(Ljava/lang/Object;Ljava/lang/String;" + typeNm + ")V", null, null);

        mv.visitCode();

        mv.visitVarInsn(ALOAD, 0);
        mv.visitFieldInsn(GETFIELD, accessClassNm, "fieldNames", "[Ljava/lang/String;");
        mv.visitVarInsn(ALOAD, 2);
        mv.visitMethodInsn(INVOKESTATIC, "java/util/Arrays", "binarySearch",
                "([Ljava/lang/Object;Ljava/lang/Object;)I");

        mv.visitVarInsn(ISTORE, 4 + offset);
        mv.visitVarInsn(ILOAD, 4 + offset);

        if (fields.length > 0) {
            maxStack = 6;

            Label[] labels = new Label[fields.length];
            Label labelForInvalidTypes = new Label();
            boolean hasAnyBadTypeLabel = false;

            for (int i = 0, n = labels.length; i < n; i++) {
                if (Type.getType(fields[i].getType()).equals(type))
                    labels[i] = new Label();
                else {
                    labels[i] = labelForInvalidTypes;
                    hasAnyBadTypeLabel = true;
                }
            }
            Label defaultLabel = new Label();
            mv.visitTableSwitchInsn(0, labels.length - 1, defaultLabel, labels);

            for (int i = 0, n = labels.length; i < n; i++) {
                if (!labels[i].equals(labelForInvalidTypes)) {
                    Field field = fields[i];

                    mv.visitLabel(labels[i]);
                    mv.visitFrame(F_SAME, 0, null, 0, null);
                    mv.visitVarInsn(ALOAD, 1);
                    mv.visitTypeInsn(CHECKCAST, clazzNm);

                    mv.visitVarInsn(instruction, 3);
                    mv.visitFieldInsn(PUTFIELD, clazzNm, field.getName(), typeNm);
                    mv.visitInsn(RETURN);
                }
            }

            if (hasAnyBadTypeLabel) {
                mv.visitLabel(labelForInvalidTypes);
                mv.visitFrame(F_SAME, 0, null, 0, null);
                enhanceForThrowingException(mv, IllegalArgumentException.class, type.getClassName(), typeNm,
                        instruction, 3);
            }

            mv.visitLabel(defaultLabel);
            mv.visitFrame(F_SAME, 0, null, 0, null);
        }

        final int maxLocals = 5 + offset;

        enhanceForThrowingException(mv, IllegalArgumentException.class, "Field was not found", typeNm, instruction,
                3);
        mv.visitMaxs(maxStack, maxLocals);
        mv.visitEnd();
    }

    private static void enhanceForGetValuePrimitive(ClassVisitor cw, String accessClassNm, String clazzNm,
            Field[] fields, Type type) {

        String methodName;
        final String typeNm = type.getDescriptor();
        final int instruction;

        switch (type.getSort()) {
        case Type.BOOLEAN:
            methodName = "getBooleanValue";
            instruction = IRETURN;
            break;
        case Type.BYTE:
            methodName = "getByteValue";
            instruction = IRETURN;
            break;
        case Type.CHAR:
            methodName = "getCharValue";
            instruction = IRETURN;
            break;
        case Type.SHORT:
            methodName = "getShortValue";
            instruction = IRETURN;
            break;
        case Type.INT:
            methodName = "getIntValue";
            instruction = IRETURN;
            break;
        case Type.FLOAT:
            methodName = "getFloatValue";
            instruction = FRETURN;
            break;
        case Type.LONG:
            methodName = "getLongValue";
            instruction = LRETURN;
            break;
        case Type.DOUBLE:
            methodName = "getDoubleValue";
            instruction = DRETURN;
            break;
        default:
            methodName = "getValue";
            instruction = ARETURN;
            break;
        }

        MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, methodName, "(Ljava/lang/Object;Ljava/lang/String;)" + typeNm,
                null, null);

        mv.visitCode();
        mv.visitVarInsn(ALOAD, 0);
        mv.visitFieldInsn(GETFIELD, accessClassNm, "fieldNames", "[Ljava/lang/String;");
        mv.visitVarInsn(ALOAD, 2);
        mv.visitMethodInsn(INVOKESTATIC, "java/util/Arrays", "binarySearch",
                "([Ljava/lang/Object;Ljava/lang/Object;)I");
        mv.visitVarInsn(ISTORE, 3);
        mv.visitVarInsn(ILOAD, 3);

        final int maxStack;

        if (fields.length > 0) {
            maxStack = 5;
            Label[] labels = constructLabels(fields);

            Label defaultLabel = new Label();
            mv.visitTableSwitchInsn(0, labels.length - 1, defaultLabel, labels);

            for (int i = 0, n = labels.length; i < n; i++) {
                Field field = fields[i];
                mv.visitLabel(labels[i]);
                mv.visitFrame(F_SAME, 0, null, 0, null);
                mv.visitVarInsn(ALOAD, 1);
                mv.visitTypeInsn(CHECKCAST, clazzNm);
                mv.visitFieldInsn(GETFIELD, clazzNm, field.getName(), typeNm);
                mv.visitInsn(instruction);
            }

            mv.visitLabel(defaultLabel);
            mv.visitFrame(F_SAME, 0, null, 0, null);
        } else {
            maxStack = 6;
        }
        enhanceForThrowingException(mv, IllegalArgumentException.class, "Field was not found", "Ljava/lang/Object;",
                ALOAD, 2);
        mv.visitMaxs(maxStack, 4);
        mv.visitEnd();
    }

    private static void enhanceForThrowingException(MethodVisitor mv, Class<? extends Exception> exceptionClass,
            String msg, String argType, int instruction, int slot) {

        String exceptionClassNm = Type.getInternalName(exceptionClass);
        mv.visitTypeInsn(NEW, exceptionClassNm);
        mv.visitInsn(DUP);
        mv.visitTypeInsn(NEW, "java/lang/StringBuilder");
        mv.visitInsn(DUP);
        mv.visitLdcInsn(msg);
        mv.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "(Ljava/lang/String;)V");
        mv.visitVarInsn(instruction, slot);
        mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append",
                "(" + argType + ")Ljava/lang/StringBuilder;");
        mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;");
        mv.visitMethodInsn(INVOKESPECIAL, exceptionClassNm, "<init>", "(Ljava/lang/String;)V");
        mv.visitInsn(ATHROW);
    }

    private static void enhanceForInvokeMethod(ClassVisitor cw, String accessClassNm, String clazzNm,
            Method[] methods) {

        MethodVisitor mv = cw.visitMethod(ACC_PUBLIC + ACC_VARARGS, "invokeMethod",
                "(Ljava/lang/Object;Ljava/lang/reflect/Method;[Ljava/lang/Object;)Ljava/lang/Object;", null, null);
        mv.visitCode();

        if (methods.length > 0) {

            mv.visitVarInsn(ALOAD, 1);
            mv.visitTypeInsn(CHECKCAST, clazzNm);

            mv.visitVarInsn(ASTORE, 4);
            mv.visitVarInsn(ALOAD, 0);
            mv.visitVarInsn(ALOAD, 2);
            mv.visitMethodInsn(INVOKESPECIAL, "org/jadira/reflection/access/asm/AsmClassAccess", "getMethodIndex",
                    "(Ljava/lang/reflect/Method;)I");
            mv.visitVarInsn(ISTORE, 5);
            mv.visitVarInsn(ILOAD, 5);

            Label[] labels = constructLabels(methods);
            Label defaultLabel = new Label();

            mv.visitTableSwitchInsn(0, labels.length - 1, defaultLabel, labels);

            for (int i = 0; i < labels.length; i++) {

                Method method = methods[i];
                Class<?>[] parameterTypes = method.getParameterTypes();

                mv.visitLabel(labels[i]);

                if (i == 0) {
                    mv.visitFrame(F_APPEND, 1, new Object[] { clazzNm }, 0, null);
                } else {
                    mv.visitFrame(F_SAME, 0, null, 0, null);
                }
                mv.visitVarInsn(ALOAD, 4);

                StringBuilder methodDescriptor = new StringBuilder("(");
                for (int parameterIdx = 0; parameterIdx < parameterTypes.length; parameterIdx++) {

                    Type type = Type.getType(parameterTypes[parameterIdx]);

                    mv.visitVarInsn(ALOAD, 3);
                    mv.visitIntInsn(BIPUSH, parameterIdx);
                    mv.visitInsn(AALOAD);

                    switch (type.getSort()) {
                    case Type.BOOLEAN:
                        mv.visitTypeInsn(CHECKCAST, "java/lang/Boolean");
                        mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Boolean", "booleanValue", "()Z");
                        break;
                    case Type.BYTE:
                        mv.visitTypeInsn(CHECKCAST, "java/lang/Byte");
                        mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Byte", "byteValue", "()B");
                        break;
                    case Type.CHAR:
                        mv.visitTypeInsn(CHECKCAST, "java/lang/Character");
                        mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Character", "charValue", "()C");
                        break;
                    case Type.SHORT:
                        mv.visitTypeInsn(CHECKCAST, "java/lang/Short");
                        mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Short", "shortValue", "()S");
                        break;
                    case Type.INT:
                        mv.visitTypeInsn(CHECKCAST, "java/lang/Integer");
                        mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Integer", "intValue", "()I");
                        break;
                    case Type.FLOAT:
                        mv.visitTypeInsn(CHECKCAST, "java/lang/Float");
                        mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Float", "floatValue", "()F");
                        break;
                    case Type.LONG:
                        mv.visitTypeInsn(CHECKCAST, "java/lang/Long");
                        mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Long", "longValue", "()J");
                        break;
                    case Type.DOUBLE:
                        mv.visitTypeInsn(CHECKCAST, "java/lang/Double");
                        mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Double", "doubleValue", "()D");
                        break;
                    case Type.ARRAY:
                        mv.visitTypeInsn(CHECKCAST, type.getDescriptor());
                        break;
                    case Type.OBJECT:
                        mv.visitTypeInsn(CHECKCAST, type.getInternalName());
                        break;
                    }
                    methodDescriptor.append(type.getDescriptor());
                }

                methodDescriptor.append(')');
                methodDescriptor.append(Type.getDescriptor(method.getReturnType()));

                mv.visitMethodInsn(INVOKEVIRTUAL, clazzNm, method.getName(), methodDescriptor.toString());

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

                mv.visitInsn(ARETURN);
            }

            mv.visitLabel(defaultLabel);
            mv.visitFrame(F_SAME, 0, null, 0, null);
        }

        enhanceForThrowingException(mv, IllegalArgumentException.class, "Method not found", "Ljava/lang/Object;",
                ALOAD, 2);

        mv.visitMaxs(8, 6);
        mv.visitEnd();
    }

    @Override
    public abstract C newInstance();

    /**
     * Retrieve the value of the field for the given instance
     * @param object The instance to access the field for
     * @param fieldName Name of the field to access
     * @return The field value as an object
     */
    public abstract Object getValue(C object, String fieldName);

    /**
     * Update the value of the field for the given instance
     * @param object The instance to access the field for
     * @param fieldName Name of the field to access
     * @param value The new value
     */
    public abstract void putValue(C object, String fieldName, Object value);

    /**
     * Retrieve the value of the field for the given instance
     * @param object The instance to access the field for
     * @param fieldName Name of the field to access
     * @return The field value as a boolean
     */
    public abstract boolean getBooleanValue(C object, String fieldName);

    /**
     * Update the value of the field for the given instance
     * @param object The instance to access the field for
     * @param fieldName Name of the field to access
     * @param value The new boolean value
     */
    public abstract void putBooleanValue(C object, String fieldName, boolean value);

    /**
     * Retrieve the value of the field for the given instance
     * @param object The instance to access the field for
     * @param fieldName Name of the field to access
     * @return The field value as a byte
     */
    public abstract byte getByteValue(C object, String fieldName);

    /**
     * Update the value of the field for the given instance
     * @param object The instance to access the field for
     * @param fieldName Name of the field to access
     * @param value The new byte value
     */
    public abstract void putByteValue(C object, String fieldName, byte value);

    /**
     * Retrieve the value of the field for the given instance
     * @param object The instance to access the field for
     * @param fieldName Name of the field to access
     * @return The field value as a char
     */
    public abstract char getCharValue(C object, String fieldName);

    /**
     * Update the value of the field for the given instance
     * @param object The instance to access the field for
     * @param fieldName Name of the field to access
     * @param value The new char value
     */
    public abstract void putCharValue(C object, String fieldName, char value);

    /**
     * Retrieve the value of the field for the given instance
     * @param object The instance to access the field for
     * @param fieldName Name of the field to access
     * @return The field value as a short
     */
    public abstract short getShortValue(C object, String fieldName);

    /**
     * Update the value of the field for the given instance
     * @param object The instance to access the field for
     * @param fieldName Name of the field to access
     * @param value The new short value
     */
    public abstract void putShortValue(C object, String fieldName, short value);

    /**
     * Retrieve the value of the field for the given instance
     * @param object The instance to access the field for
     * @param fieldName Name of the field to access
     * @return The field value as a int
     */
    public abstract int getIntValue(C object, String fieldName);

    /**
     * Update the value of the field for the given instance
     * @param object The instance to access the field for
     * @param fieldName Name of the field to access
     * @param value The new int value
     */
    public abstract void putIntValue(C object, String fieldName, int value);

    /**
     * Retrieve the value of the field for the given instance
     * @param object The instance to access the field for
     * @param fieldName Name of the field to access
     * @return The field value as a long
     */
    public abstract long getLongValue(C object, String fieldName);

    /**
     * Update the value of the field for the given instance
     * @param object The instance to access the field for
     * @param fieldName Name of the field to access
     * @param value The new long value
     */
    public abstract void putLongValue(C object, String fieldName, long value);

    /**
     * Retrieve the value of the field for the given instance
     * @param object The instance to access the field for
     * @param fieldName Name of the field to access
     * @return The field value as a float
     */
    public abstract float getFloatValue(C object, String fieldName);

    /**
     * Update the value of the field for the given instance
     * @param object The instance to access the field for
     * @param fieldName Name of the field to access
     * @param value The new float value
     */
    public abstract void putFloatValue(C object, String fieldName, float value);

    /**
     * Retrieve the value of the field for the given instance
     * @param object The instance to access the field for
     * @param fieldName Name of the field to access
     * @return The field value as a double
     */
    public abstract double getDoubleValue(C object, String fieldName);

    /**
     * Update the value of the field for the given instance
     * @param object The instance to access the field for
     * @param fieldName Name of the field to access
     * @param value The new double value
     */
    public abstract void putDoubleValue(C object, String fieldName, double value);

    /**
     * Invoke the given method on the given instance
     * @param target The target object
     * @param method The method to invoke
     * @param args The parameters to pass
     * @return The result of the invocation (null for a void method)
     */
    public abstract Object invokeMethod(Object target, Method method, Object[] args);

    String foo = new String();

    public Object invokeMethod2(Object target, Method method, Object[] args) {
        return getMethodIndex(method);
    }

    private int getMethodIndex(Method m) {
        int idx = Arrays.binarySearch(getDeclaredMethodNames(), m.getName());
        if (getDeclaredMethodAccessors()[idx].method().equals(m)) {
            return idx;
        }
        int backTrack = idx;
        while (true) {
            if (getDeclaredMethodAccessors()[backTrack].method().equals(m)) {
                return idx;
            }
            if (!(getDeclaredMethodAccessors()[backTrack].method().getName().equals(m.getName()))) {
                break;
            }
            backTrack = backTrack - 1;
        }
        while (true) {
            idx = idx + 1;
            if (getDeclaredMethodAccessors()[idx].method().equals(m)) {
                return idx;
            }
            if (!(getDeclaredMethodAccessors()[idx].method().getName().equals(m.getName()))) {
                break;
            }
        }
        return -1;
    }

    @Override
    protected MethodAccess<C> constructMethodAccess(Method method) {
        return AsmMethodAccess.get(this, method);
    }

    @Override
    protected FieldAccess<C> constructFieldAccess(Field field) {
        if ((field.getModifiers() & Modifier.PRIVATE) != 0) {
            return PortableFieldAccess.get(field);
        } else {
            return AsmFieldAccess.get(this, field);
        }
    }

    @Override
    protected <X> ClassAccess<X> constructClassAccess(Class<X> clazz) {
        return AsmClassAccess.get(clazz);
    }
}