kr.debop4j.core.reflect.FieldAccess.java Source code

Java tutorial

Introduction

Here is the source code for kr.debop4j.core.reflect.FieldAccess.java

Source

/*
 * Copyright 2011-2013 the original author or authors.
 *
 * 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 kr.debop4j.core.reflect;

import com.google.common.collect.Lists;
import kr.debop4j.core.Guard;
import lombok.extern.slf4j.Slf4j;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Type;

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

import static org.objectweb.asm.Opcodes.*;

/**
 * ?? ??   Accessor . (??  get, set?   )
 *
 * @author ? ( sunghyouk.bae@gmail.com )
 * @since 13. 1. 21
 */
@Slf4j
public abstract class FieldAccess {

    private String[] fieldNames;

    /**
     * Gets index.
     *
     * @param fieldName the field name
     * @return the index
     */
    public int getIndex(String fieldName) {
        for (int i = 0, n = fieldNames.length; i < n; i++)
            if (fieldNames[i].equals(fieldName))
                return i;
        throw new IllegalArgumentException("Unable to find public field: " + fieldName);
    }

    /**
     * Set void.
     *
     * @param instance the instance
     * @param fieldName the field name
     * @param value the value
     */
    public void set(Object instance, String fieldName, Object value) {

        log.trace("?[{}]? ?[{}]? [{}] ? .", instance, fieldName, value);
        set(instance, getIndex(fieldName), value);
    }

    /**
     * Get object.
     *
     * @param instance the instance
     * @param fieldName the field name
     * @return the object
     */
    public Object get(Object instance, String fieldName) {

        log.trace("?[{}]? ?[{}] ? .", instance, fieldName);
        return get(instance, getIndex(fieldName));
    }

    /**
     * Get field names.
     *
     * @return the string [ ]
     */
    public String[] getFieldNames() {
        return Arrays.copyOf(fieldNames, fieldNames.length);
    }

    /**
     * Set void.
     *
     * @param instance the instance
     * @param fieldIndex the field index
     * @param value the value
     */
    abstract public void set(Object instance, int fieldIndex, Object value);

    /**
     * Sets boolean.
     *
     * @param instance the instance
     * @param fieldIndex the field index
     * @param value the value
     */
    abstract public void setBoolean(Object instance, int fieldIndex, boolean value);

    /**
     * Sets byte.
     *
     * @param instance the instance
     * @param fieldIndex the field index
     * @param value the value
     */
    abstract public void setByte(Object instance, int fieldIndex, byte value);

    /**
     * Sets short.
     *
     * @param instance the instance
     * @param fieldIndex the field index
     * @param value the value
     */
    abstract public void setShort(Object instance, int fieldIndex, short value);

    /**
     * Sets int.
     *
     * @param instance the instance
     * @param fieldIndex the field index
     * @param value the value
     */
    abstract public void setInt(Object instance, int fieldIndex, int value);

    /**
     * Sets long.
     *
     * @param instance the instance
     * @param fieldIndex the field index
     * @param value the value
     */
    abstract public void setLong(Object instance, int fieldIndex, long value);

    /**
     * Sets double.
     *
     * @param instance the instance
     * @param fieldIndex the field index
     * @param value the value
     */
    abstract public void setDouble(Object instance, int fieldIndex, double value);

    /**
     * Sets float.
     *
     * @param instance the instance
     * @param fieldIndex the field index
     * @param value the value
     */
    abstract public void setFloat(Object instance, int fieldIndex, float value);

    /**
     * Sets char.
     *
     * @param instance the instance
     * @param fieldIndex the field index
     * @param value the value
     */
    abstract public void setChar(Object instance, int fieldIndex, char value);

    /**
     * Get object.
     *
     * @param instance the instance
     * @param fieldIndex the field index
     * @return the object
     */
    abstract public Object get(Object instance, int fieldIndex);

    /**
     * Gets string.
     *
     * @param instance the instance
     * @param fieldIndex the field index
     * @return the string
     */
    abstract public String getString(Object instance, int fieldIndex);

    /**
     * Gets char.
     *
     * @param instance the instance
     * @param fieldIndex the field index
     * @return the char
     */
    abstract public char getChar(Object instance, int fieldIndex);

    /**
     * Gets boolean.
     *
     * @param instance the instance
     * @param fieldIndex the field index
     * @return the boolean
     */
    abstract public boolean getBoolean(Object instance, int fieldIndex);

    /**
     * Gets byte.
     *
     * @param instance the instance
     * @param fieldIndex the field index
     * @return the byte
     */
    abstract public byte getByte(Object instance, int fieldIndex);

    /**
     * Gets short.
     *
     * @param instance the instance
     * @param fieldIndex the field index
     * @return the short
     */
    abstract public short getShort(Object instance, int fieldIndex);

    /**
     * Gets int.
     *
     * @param instance the instance
     * @param fieldIndex the field index
     * @return the int
     */
    abstract public int getInt(Object instance, int fieldIndex);

    /**
     * Gets long.
     *
     * @param instance the instance
     * @param fieldIndex the field index
     * @return the long
     */
    abstract public long getLong(Object instance, int fieldIndex);

    /**
     * Gets double.
     *
     * @param instance the instance
     * @param fieldIndex the field index
     * @return the double
     */
    abstract public double getDouble(Object instance, int fieldIndex);

    /**
     * Gets float.
     *
     * @param instance the instance
     * @param fieldIndex the field index
     * @return the float
     */
    abstract public float getFloat(Object instance, int fieldIndex);

    /**
     * Get field access.
     *
     * @param type the type
     * @return the field access
     */
    static public FieldAccess get(Class type) {
        Guard.shouldNotBeNull(type, "type");

        List<Field> fields = Lists.newArrayList();
        Class nextClass = type;
        while (nextClass != Object.class) {
            Field[] declaredFields = nextClass.getDeclaredFields();
            for (Field field : declaredFields) {
                int modifiers = field.getModifiers();
                if (Modifier.isStatic(modifiers))
                    continue;
                if (Modifier.isPrivate(modifiers))
                    continue;
                fields.add(field);
            }
            nextClass = nextClass.getSuperclass();
        }

        String[] fieldNames = new String[fields.size()];
        for (int i = 0, n = fieldNames.length; i < n; i++)
            fieldNames[i] = fields.get(i).getName();

        String className = type.getName();
        String accessClassName = className + "FieldAccess";
        if (accessClassName.startsWith("java."))
            accessClassName = ReflectConsts.BASE_PACKAGE + "." + accessClassName;
        Class accessClass = null;

        AccessClassLoader loader = AccessClassLoader.get(type);
        synchronized (loader) {
            try {
                accessClass = loader.loadClass(accessClassName);
            } catch (ClassNotFoundException ignored) {
                String accessClassNameInternal = accessClassName.replace('.', '/');
                String classNameInternal = className.replace('.', '/');

                ClassWriter cw = new ClassWriter(0);
                cw.visit(V1_1, ACC_PUBLIC + ACC_SUPER, accessClassNameInternal, null,
                        ReflectConsts.FIELD_ACCESS_PATH, null);
                insertConstructor(cw);
                insertGetObject(cw, classNameInternal, fields);
                insertSetObject(cw, classNameInternal, fields);
                insertGetPrimitive(cw, classNameInternal, fields, Type.BOOLEAN_TYPE);
                insertSetPrimitive(cw, classNameInternal, fields, Type.BOOLEAN_TYPE);
                insertGetPrimitive(cw, classNameInternal, fields, Type.BYTE_TYPE);
                insertSetPrimitive(cw, classNameInternal, fields, Type.BYTE_TYPE);
                insertGetPrimitive(cw, classNameInternal, fields, Type.SHORT_TYPE);
                insertSetPrimitive(cw, classNameInternal, fields, Type.SHORT_TYPE);
                insertGetPrimitive(cw, classNameInternal, fields, Type.INT_TYPE);
                insertSetPrimitive(cw, classNameInternal, fields, Type.INT_TYPE);
                insertGetPrimitive(cw, classNameInternal, fields, Type.LONG_TYPE);
                insertSetPrimitive(cw, classNameInternal, fields, Type.LONG_TYPE);
                insertGetPrimitive(cw, classNameInternal, fields, Type.DOUBLE_TYPE);
                insertSetPrimitive(cw, classNameInternal, fields, Type.DOUBLE_TYPE);
                insertGetPrimitive(cw, classNameInternal, fields, Type.FLOAT_TYPE);
                insertSetPrimitive(cw, classNameInternal, fields, Type.FLOAT_TYPE);
                insertGetPrimitive(cw, classNameInternal, fields, Type.CHAR_TYPE);
                insertSetPrimitive(cw, classNameInternal, fields, Type.CHAR_TYPE);
                insertGetString(cw, classNameInternal, fields);
                cw.visitEnd();
                accessClass = loader.defineClass(accessClassName, cw.toByteArray());
            }
        }
        try {
            FieldAccess access = (FieldAccess) accessClass.newInstance();
            access.fieldNames = fieldNames;
            return access;
        } catch (Exception ex) {
            throw new RuntimeException("Error constructing field access class: " + accessClassName, ex);
        }
    }

    static private void insertConstructor(ClassWriter cw) {
        MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
        mv.visitCode();
        mv.visitVarInsn(ALOAD, 0);
        mv.visitMethodInsn(INVOKESPECIAL, ReflectConsts.FIELD_ACCESS_PATH, "<init>", "()V");
        mv.visitInsn(RETURN);
        mv.visitMaxs(1, 1);
        mv.visitEnd();
    }

    static private void insertSetObject(ClassWriter cw, String classNameInternal, List<Field> fields) {
        int maxStack = 6;
        MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "set", "(Ljava/lang/Object;ILjava/lang/Object;)V", null,
                null);
        mv.visitCode();
        mv.visitVarInsn(ILOAD, 2);

        if (!fields.isEmpty()) {
            maxStack--;
            Label[] labels = new Label[fields.size()];
            for (int i = 0, n = labels.length; i < n; i++)
                labels[i] = new Label();
            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.get(i);
                Type fieldType = Type.getType(field.getType());

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

                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;
                default:
                }

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

            mv.visitLabel(defaultLabel);
            mv.visitFrame(F_SAME, 0, null, 0, null);
        }
        mv = insertThrowExceptionForFieldNotFound(mv);
        mv.visitMaxs(maxStack, 4);
        mv.visitEnd();
    }

    static private void insertGetObject(ClassWriter cw, String classNameInternal, List<Field> fields) {
        int maxStack = 6;
        MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "get", "(Ljava/lang/Object;I)Ljava/lang/Object;", null, null);
        mv.visitCode();
        mv.visitVarInsn(ILOAD, 2);

        if (!fields.isEmpty()) {
            maxStack--;
            Label[] labels = new Label[fields.size()];
            for (int i = 0, n = labels.length; i < n; i++)
                labels[i] = new Label();
            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.get(i);

                mv.visitLabel(labels[i]);
                mv.visitFrame(F_SAME, 0, null, 0, null);
                mv.visitVarInsn(ALOAD, 1);
                mv.visitTypeInsn(CHECKCAST, classNameInternal);
                mv.visitFieldInsn(GETFIELD, classNameInternal, 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;
                default:
                }

                mv.visitInsn(ARETURN);
            }

            mv.visitLabel(defaultLabel);
            mv.visitFrame(F_SAME, 0, null, 0, null);
        }
        insertThrowExceptionForFieldNotFound(mv);
        mv.visitMaxs(maxStack, 3);
        mv.visitEnd();
    }

    static private void insertGetString(ClassWriter cw, String classNameInternal, List<Field> fields) {
        int maxStack = 6;
        MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "getString", "(Ljava/lang/Object;I)Ljava/lang/String;", null,
                null);
        mv.visitCode();
        mv.visitVarInsn(ILOAD, 2);

        if (!fields.isEmpty()) {
            maxStack--;
            Label[] labels = new Label[fields.size()];
            Label labelForInvalidTypes = new Label();
            boolean hasAnyBadTypeLabel = false;
            for (int i = 0, n = labels.length; i < n; i++) {
                if (fields.get(i).getType().equals(String.class))
                    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)) {
                    mv.visitLabel(labels[i]);
                    mv.visitFrame(F_SAME, 0, null, 0, null);
                    mv.visitVarInsn(ALOAD, 1);
                    mv.visitTypeInsn(CHECKCAST, classNameInternal);
                    mv.visitFieldInsn(GETFIELD, classNameInternal, fields.get(i).getName(), "Ljava/lang/String;");
                    mv.visitInsn(ARETURN);
                }
            }
            // Rest of fields: different type
            if (hasAnyBadTypeLabel) {
                mv.visitLabel(labelForInvalidTypes);
                mv.visitFrame(F_SAME, 0, null, 0, null);
                insertThrowExceptionForFieldType(mv, "String");
            }
            // Default: field not found
            mv.visitLabel(defaultLabel);
            mv.visitFrame(F_SAME, 0, null, 0, null);
        }
        insertThrowExceptionForFieldNotFound(mv);
        mv.visitMaxs(maxStack, 3);
        mv.visitEnd();
    }

    static private void insertSetPrimitive(ClassWriter cw, String classNameInternal, List<Field> fields,
            Type primitiveType) {
        int maxStack = 6;
        int maxLocals = 4; // See correction below for LLOAD and DLOAD
        final String setterMethodName;
        final String typeNameInternal = primitiveType.getDescriptor();
        final int loadValueInstruction;
        switch (primitiveType.getSort()) {
        case Type.BOOLEAN:
            setterMethodName = "setBoolean";
            loadValueInstruction = ILOAD;
            break;
        case Type.BYTE:
            setterMethodName = "setByte";
            loadValueInstruction = ILOAD;
            break;
        case Type.CHAR:
            setterMethodName = "setChar";
            loadValueInstruction = ILOAD;
            break;
        case Type.SHORT:
            setterMethodName = "setShort";
            loadValueInstruction = ILOAD;
            break;
        case Type.INT:
            setterMethodName = "setInt";
            loadValueInstruction = ILOAD;
            break;
        case Type.FLOAT:
            setterMethodName = "setFloat";
            loadValueInstruction = FLOAD;
            break;
        case Type.LONG:
            setterMethodName = "setLong";
            loadValueInstruction = LLOAD;
            maxLocals++; // (LLOAD and DLOAD actually load two slots)
            break;
        case Type.DOUBLE:
            setterMethodName = "setDouble";
            loadValueInstruction = DLOAD; // (LLOAD and DLOAD actually load two slots)
            maxLocals++;
            break;
        default:
            setterMethodName = "set";
            loadValueInstruction = ALOAD;
            break;
        }
        MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, setterMethodName,
                "(Ljava/lang/Object;I" + typeNameInternal + ")V", null, null);
        mv.visitCode();
        mv.visitVarInsn(ILOAD, 2);

        if (!fields.isEmpty()) {
            maxStack--;
            Label[] labels = new Label[fields.size()];
            Label labelForInvalidTypes = new Label();
            boolean hasAnyBadTypeLabel = false;
            for (int i = 0, n = labels.length; i < n; i++) {
                if (Type.getType(fields.get(i).getType()).equals(primitiveType))
                    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)) {
                    mv.visitLabel(labels[i]);
                    mv.visitFrame(F_SAME, 0, null, 0, null);
                    mv.visitVarInsn(ALOAD, 1);
                    mv.visitTypeInsn(CHECKCAST, classNameInternal);
                    mv.visitVarInsn(loadValueInstruction, 3);
                    mv.visitFieldInsn(PUTFIELD, classNameInternal, fields.get(i).getName(), typeNameInternal);
                    mv.visitInsn(RETURN);
                }
            }
            // Rest of fields: different type
            if (hasAnyBadTypeLabel) {
                mv.visitLabel(labelForInvalidTypes);
                mv.visitFrame(F_SAME, 0, null, 0, null);
                insertThrowExceptionForFieldType(mv, primitiveType.getClassName());
            }
            // Default: field not found
            mv.visitLabel(defaultLabel);
            mv.visitFrame(F_SAME, 0, null, 0, null);
        }
        mv = insertThrowExceptionForFieldNotFound(mv);
        mv.visitMaxs(maxStack, maxLocals);
        mv.visitEnd();
    }

    static private void insertGetPrimitive(ClassWriter cw, String classNameInternal, List<Field> fields,
            Type primitiveType) {
        int maxStack = 6;
        final String getterMethodName;
        final String typeNameInternal = primitiveType.getDescriptor();
        final int returnValueInstruction;
        switch (primitiveType.getSort()) {
        case Type.BOOLEAN:
            getterMethodName = "getBoolean";
            returnValueInstruction = IRETURN;
            break;
        case Type.BYTE:
            getterMethodName = "getByte";
            returnValueInstruction = IRETURN;
            break;
        case Type.CHAR:
            getterMethodName = "getChar";
            returnValueInstruction = IRETURN;
            break;
        case Type.SHORT:
            getterMethodName = "getShort";
            returnValueInstruction = IRETURN;
            break;
        case Type.INT:
            getterMethodName = "getInt";
            returnValueInstruction = IRETURN;
            break;
        case Type.FLOAT:
            getterMethodName = "getFloat";
            returnValueInstruction = FRETURN;
            break;
        case Type.LONG:
            getterMethodName = "getLong";
            returnValueInstruction = LRETURN;
            break;
        case Type.DOUBLE:
            getterMethodName = "getDouble";
            returnValueInstruction = DRETURN;
            break;
        default:
            getterMethodName = "get";
            returnValueInstruction = ARETURN;
            break;
        }
        MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, getterMethodName, "(Ljava/lang/Object;I)" + typeNameInternal,
                null, null);
        mv.visitCode();
        mv.visitVarInsn(ILOAD, 2);

        if (!fields.isEmpty()) {
            maxStack--;
            Label[] labels = new Label[fields.size()];
            Label labelForInvalidTypes = new Label();
            boolean hasAnyBadTypeLabel = false;
            for (int i = 0, n = labels.length; i < n; i++) {
                if (Type.getType(fields.get(i).getType()).equals(primitiveType))
                    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++) {
                Field field = fields.get(i);
                if (!labels[i].equals(labelForInvalidTypes)) {
                    mv.visitLabel(labels[i]);
                    mv.visitFrame(F_SAME, 0, null, 0, null);
                    mv.visitVarInsn(ALOAD, 1);
                    mv.visitTypeInsn(CHECKCAST, classNameInternal);
                    mv.visitFieldInsn(GETFIELD, classNameInternal, field.getName(), typeNameInternal);
                    mv.visitInsn(returnValueInstruction);
                }
            }
            // Rest of fields: different type
            if (hasAnyBadTypeLabel) {
                mv.visitLabel(labelForInvalidTypes);
                mv.visitFrame(F_SAME, 0, null, 0, null);
                insertThrowExceptionForFieldType(mv, primitiveType.getClassName());
            }
            // Default: field not found
            mv.visitLabel(defaultLabel);
            mv.visitFrame(F_SAME, 0, null, 0, null);
        }
        mv = insertThrowExceptionForFieldNotFound(mv);
        mv.visitMaxs(maxStack, 3);
        mv.visitEnd();
    }

    static private MethodVisitor insertThrowExceptionForFieldNotFound(MethodVisitor mv) {
        mv.visitTypeInsn(NEW, "java/lang/IllegalArgumentException");
        mv.visitInsn(DUP);
        mv.visitTypeInsn(NEW, "java/lang/StringBuilder");
        mv.visitInsn(DUP);
        mv.visitLdcInsn("Field not found: ");
        mv.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "(Ljava/lang/String;)V");
        mv.visitVarInsn(ILOAD, 2);
        mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(I)Ljava/lang/StringBuilder;");
        mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;");
        mv.visitMethodInsn(INVOKESPECIAL, "java/lang/IllegalArgumentException", "<init>", "(Ljava/lang/String;)V");
        mv.visitInsn(ATHROW);
        return mv;
    }

    static private MethodVisitor insertThrowExceptionForFieldType(MethodVisitor mv, String fieldType) {
        mv.visitTypeInsn(NEW, "java/lang/IllegalArgumentException");
        mv.visitInsn(DUP);
        mv.visitTypeInsn(NEW, "java/lang/StringBuilder");
        mv.visitInsn(DUP);
        mv.visitLdcInsn("Field not declared as " + fieldType + ": ");
        mv.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "(Ljava/lang/String;)V");
        mv.visitVarInsn(ILOAD, 2);
        mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(I)Ljava/lang/StringBuilder;");
        mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;");
        mv.visitMethodInsn(INVOKESPECIAL, "java/lang/IllegalArgumentException", "<init>", "(Ljava/lang/String;)V");
        mv.visitInsn(ATHROW);
        return mv;
    }
}