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

org.babyfish.model.instrument.spi.ObjectModelTargetGenerator Maven / Gradle / Ivy

Go to download

This tool project supports some maven plugins of BabyFish, it shouldn't used by the user directly.

The newest version!
/*
 * BabyFish, Object Model Framework for Java and JPA.
 * https://github.com/babyfish-ct/babyfish
 *
 * Copyright (c) 2008-2016, Tao Chen
 *
 * This copyrighted material is made available to anyone wishing to use, modify,
 * copy, or redistribute it subject to the terms and conditions of the GNU
 * Lesser General Public License, as published by the Free Software Foundation.
 *
 * Please visit "http://opensource.org/licenses/LGPL-3.0" to know more.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License
 * for more details.
 */
package org.babyfish.model.instrument.spi;

import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.SortedMap;
import java.util.SortedSet;

import org.babyfish.collection.ArrayList;
import org.babyfish.lang.bytecode.ASMUtils;
import org.babyfish.lang.bytecode.ScopedMethodVisitor;
import org.babyfish.lang.bytecode.ScopedMethodVisitorBuilder;
import org.babyfish.lang.bytecode.VariableScope;
import org.babyfish.lang.bytecode.VariableScopeBuilder;
import org.babyfish.model.instrument.metadata.MetadataClass;
import org.babyfish.model.instrument.metadata.MetadataProperty;
import org.babyfish.model.metadata.AssociationType;
import org.babyfish.model.metadata.PropertyType;
import org.babyfish.model.spi.association.AssociatedEndpoint;
import org.babyfish.model.spi.association.AssociatedIndexedReference;
import org.babyfish.model.spi.association.AssociatedKeyedReference;
import org.babyfish.model.spi.association.AssociatedList;
import org.babyfish.model.spi.association.AssociatedNavigableMap;
import org.babyfish.model.spi.association.AssociatedNavigableSet;
import org.babyfish.model.spi.association.AssociatedOrderedMap;
import org.babyfish.model.spi.association.AssociatedOrderedSet;
import org.babyfish.model.spi.association.AssociatedReference;
import org.babyfish.org.objectweb.asm.ClassVisitor;
import org.babyfish.org.objectweb.asm.Label;
import org.babyfish.org.objectweb.asm.MethodVisitor;
import org.babyfish.org.objectweb.asm.Opcodes;
import org.babyfish.org.objectweb.asm.Type;

/**
 * @author Tao Chen
 */
public class ObjectModelTargetGenerator extends AbstractObjectModelGenerator {
    
    private boolean overrideLoadScalars;

    private boolean overrideInitAssociations;
    
    public ObjectModelTargetGenerator(AbstractModelReplacer parent) {
        super(
                parent,
                parent.isProxySupported() ? 
                        Identifiers.OBJECT_MODEL_TARGET_SIMPLE_NAME : 
                        Identifiers.OBJECT_MODEL_CONTRACT_SIMPLE_NAME
        );
        for (MetadataProperty metadataProperty : this.getMetadataClass().getDeclaredProperties().values()) {
            if (metadataProperty.getPropertyType() == PropertyType.SCALAR && metadataProperty.isDeferrable()) {
                this.overrideLoadScalars = true;
                break;
            }
        }
        for (MetadataProperty metadataProperty : this.getMetadataClass().getDeclaredProperties().values()) {
            if (metadataProperty.getPropertyType() == PropertyType.ASSOCIATION ||
                    metadataProperty.getPropertyType() == PropertyType.CONTRAVARIANCE) {
                this.overrideInitAssociations = true;
                break;
            }
        }
    }
    
    protected final String getSuperInternalName() {
        MetadataClass superClass = this.getMetadataClass().getSuperClass();
        if (superClass != null) {
            return superClass.getInternalName() + '$' + this.getSimpleName();
        } else {
            return ASMConstants.ABSTRACT_OBJECT_MODEL_IMPL_INTERNAL_NAME;
        }
    }

    @Override
    protected int determineAccess() {
        return Opcodes.ACC_PROTECTED | Opcodes.ACC_STATIC;
    }
    
    protected String[] determineInterfaces() {
        if (!this.getParent().isProxySupported()) {
            return new String[0];
        }
        return new String[] {
                this.getMetadataClass().getInternalName() + 
                '$' + 
                Identifiers.OBJECT_MODEL_CONTRACT_SIMPLE_NAME
        };
    }
    
    @Override
    protected final void generate(ClassVisitor cv) {
        cv.visit(
                this.getMetadataClass().getBytecodeVersion(), 
                this.determineAccess(), 
                this.getInternalName(),  
                null,
                this.getSuperInternalName(),
                this.determineInterfaces()
        );
        
        this.generateMembers(cv);
        
        cv.visitEnd();
    }

    private Class determinAssociatedEndpointType(MetadataProperty metadataProperty) {
        switch (metadataProperty.getPropertyType()) {
        case CONTRAVARIANCE:
        case INDEX:
        case KEY:
            throw new AssertionError("Internal bug");
        default:
        }
        return this.onDeterminAssociatedEndpointType(metadataProperty);
    }
    
    protected Class onDeterminAssociatedEndpointType(MetadataProperty metadataProperty) {
        switch (metadataProperty.getAssociationType()) {
        case LIST:
            return AssociatedList.class;
        case COLLECTION:
            if (SortedSet.class.isAssignableFrom(metadataProperty.getStandardCollectionType())) {
                return AssociatedNavigableSet.class;
            }
            return AssociatedOrderedSet.class;
        case MAP:
            if (SortedMap.class.isAssignableFrom(metadataProperty.getStandardCollectionType())) {
                return AssociatedNavigableMap.class;
            }
            return AssociatedOrderedMap.class;
        case REFERENCE:
            return AssociatedReference.class;
        case INDEXED_REFERENCE:
            return AssociatedIndexedReference.class;
        case KEYED_REFERENCE:
            return AssociatedKeyedReference.class;
        default:
            return null;
        }
    }
    
    protected final String fieldInternalName(MetadataProperty metadataProperty) {
        switch (metadataProperty.getPropertyType()) {
        case CONTRAVARIANCE:
        case INDEX:
        case KEY:
            throw new AssertionError("Internal bug");
        default:
        }
        Class associatedEndpointType = this.determinAssociatedEndpointType(metadataProperty);
        if (associatedEndpointType == null) {
            String descriptor = metadataProperty.getDescriptor();
            if (descriptor.charAt(0) == 'L') {
                return descriptor.substring(1, descriptor.length() - 1);
            }
            return descriptor;
        }
        return Type.getInternalName(associatedEndpointType);
    }
    
    protected final String fieldDescriptor(MetadataProperty metadataProperty) {
        
        Class associatedEndpointType = this.determinAssociatedEndpointType(metadataProperty);
        if (associatedEndpointType == null) {
            return metadataProperty.getDescriptor();
        }
        return Type.getDescriptor(associatedEndpointType);
    }
    
    protected final String fieldSignature(MetadataProperty metadataProperty) {
        
        StringBuilder builder = new StringBuilder();
        
        Class associatedEndpointType = this.determinAssociatedEndpointType(metadataProperty);
        if (associatedEndpointType == null) {
            return metadataProperty.getSignature();
        }
        
        builder
        .append('L')
        .append(Type.getInternalName(associatedEndpointType));
        
        switch (metadataProperty.getAssociationType()) {
        case REFERENCE:
        case INDEXED_REFERENCE:
            builder.append('<').append(this.getDescriptor()).append(">;");
            break;
        case KEYED_REFERENCE:
            builder
            .append('<')
            .append(metadataProperty.getKeyProperty().getDescriptor())
            .append(metadataProperty.getDescriptor())
            .append(">;");
            break;
        default:
            String signature = metadataProperty.getSignature();
            builder.append(signature.substring(signature.indexOf('<')));
        }
        return builder.toString();
    }
    
    private void generateMembers(ClassVisitor cv) {
        
        cv
        .visitField(Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL, 
                Identifiers.DEFERRABLE_SCALAR_PROPERTY_IDS, 
                "[I", 
                null, 
                null
        )
        .visitEnd();
        
        if (!this.getParent().isProxySupported()) {
            this.generateEmbededComparatorStaticFields(cv);
        }
        
        for (MetadataProperty metadataProperty : this.getMetadataClass().getDeclaredProperties().values()) {
            this.generatePropertyFields(cv, metadataProperty);
        }
        
        this.generateConstructor(cv);
        if (this.overrideInitAssociations) {
            this.generateInitAssociatedEndpoints(cv);
        }
        
        for (MetadataProperty metadataProperty : this.getMetadataClass().getDeclaredProperties().values()) {
            this.generatePropertyGetter(cv, metadataProperty);
            this.generatePropertySetter(cv, metadataProperty);
        }
        
        this.generateObjectOperationMethods(cv, true);
        this.generateObjectOperationMethods(cv, false);
        this.generateGetAssociatedEndpointMethod(cv);
        
        this.generateGetDisablityPropertyMethod(cv);
        this.generateSetDisablityPropertyMethod(cv, true);
        this.generateSetDisablityPropertyMethod(cv, false);
        
        this.generateIsLoadedMethod(cv);
        this.generateUnloadMethod(cv);
        this.generateLoadScalars(cv);
        
        if (!this.getParent().isProxySupported()) {
            this.generateHashCodeScalarMethod(cv, false);
            this.generateEqualsScalarMethod(cv, false);
            this.generateCompareScalarMethod(cv, false);
        }
        
        this.generateScalarFrozenContextOperationMethods(cv, true);
        this.generateScalarFrozenContextOperationMethods(cv, false);
        this.generateGetEmbeddedFrozenContextMethod(cv);
        
        this.generateMoreMembers(cv);
        
        if (this.getMetadataClass().getSuperClass() == null || !this.getMetadataClass().getDeclaredProperties().isEmpty()) {
            this.generateWriteObject(cv);
            this.generateReadObject(cv);
        }
        
        this.generateStaticConstructor(cv);
    }
    
    protected void generateMoreMembers(ClassVisitor cv) {}
    
    private void generatePropertyFields(ClassVisitor cv, MetadataProperty metadataProperty) {
        String fieldName = Identifiers.fieldName(metadataProperty);
        if (fieldName != null) {
            cv
            .visitField(
                    Opcodes.ACC_PROTECTED, 
                    fieldName, 
                    this.fieldDescriptor(metadataProperty), 
                    this.fieldSignature(metadataProperty), 
                    null
            )
            .visitEnd();
            String stateFieldName = Identifiers.stateFieldName(metadataProperty);
            String frozenContextFieldName = Identifiers.frozenContextFieldName(metadataProperty);
            if (stateFieldName != null) {
                cv
                .visitField(
                        Opcodes.ACC_PROTECTED, 
                        stateFieldName, 
                        "I", 
                        null, 
                        null
                )
                .visitEnd();
            }
            if (frozenContextFieldName != null) {
                cv
                .visitField(
                        Opcodes.ACC_PROTECTED | Opcodes.ACC_TRANSIENT, 
                        Identifiers.frozenContextFieldName(metadataProperty), 
                        ASMConstants.FROZEN_CONTEXT_DESCRIPTOR, 
                        'L' + 
                        ASMConstants.FROZEN_CONTEXT_INTERNAL_NAME + 
                        "<" +
                        this.getMetadataClass().getDescriptor() +
                        ">;",
                        null)
                .visitEnd();
            }
        }
    }

    private void generateConstructor(ClassVisitor cv) {
        
        MetadataClass superClass = this.getMetadataClass().getSuperClass();
        String superOwnerDescriptor = superClass != null ? superClass.getDescriptor() : "Ljava/lang/Object;";
        MethodVisitor mv = cv.visitMethod(
                Opcodes.ACC_PROTECTED, 
                "", 
                '(' +
                ASMConstants.MODEL_CLASS_DESCRIPTOR +
                this.getMetadataClass().getDescriptor() +
                ")V", 
                null, 
                null
        );
        mv.visitCode();
        
        try (VariableScope scope = 
                new VariableScopeBuilder()
                .parameter("this", this.getDescriptor())
                .parameter("modelClass", ASMConstants.MODEL_CLASS_DESCRIPTOR)
                .parameter("owner", superOwnerDescriptor)
                .build(mv)) {
            mv.visitVarInsn(Opcodes.ALOAD, 0);
            mv.visitVarInsn(Opcodes.ALOAD, 1);
            mv.visitVarInsn(Opcodes.ALOAD, 2);
            mv.visitMethodInsn(
                    Opcodes.INVOKESPECIAL, 
                    this.getSuperInternalName(), 
                    "", 
                    '(' +
                    ASMConstants.MODEL_CLASS_DESCRIPTOR +
                    superOwnerDescriptor +
                    ")V", 
                    false
            );
            
            if (this.overrideInitAssociations) {
                scope.load("this");
                mv.visitMethodInsn(
                        Opcodes.INVOKESPECIAL, 
                        this.getInternalName(), 
                        Identifiers.INIT_ASSOCIATED_ENDPOINTS_METHOD_NAME, 
                        "()V", 
                        false
                );
            }
            
            mv.visitInsn(Opcodes.RETURN);
        }
        
        mv.visitMaxs(0, 0);
        mv.visitEnd();
    }
    
    private void generateInitAssociatedEndpoints(ClassVisitor cv) {
        MethodVisitor mv = cv.visitMethod(
                Opcodes.ACC_PRIVATE, 
                Identifiers.INIT_ASSOCIATED_ENDPOINTS_METHOD_NAME, 
                "()V", 
                null,
                null
        );
        mv.visitCode();
        
        for (MetadataProperty metadataProperty : this.getMetadataClass().getDeclaredProperties().values()) {
            if (metadataProperty.getPropertyType() == PropertyType.ASSOCIATION) {
                String fieldInternalName = this.fieldInternalName(metadataProperty);
                mv.visitVarInsn(Opcodes.ALOAD, 0);
                mv.visitTypeInsn(Opcodes.NEW, fieldInternalName);
                mv.visitInsn(Opcodes.DUP);
                mv.visitVarInsn(Opcodes.ALOAD, 0);
                mv.visitLdcInsn(metadataProperty.getId());
                mv.visitMethodInsn(
                        Opcodes.INVOKESPECIAL, 
                        fieldInternalName, 
                        "", 
                        '(' + ASMConstants.OBJECT_MODEL_DESCRIPTOR + "I)V", 
                        false
                );
                mv.visitFieldInsn(
                        Opcodes.PUTFIELD, 
                        this.getInternalName(), 
                        Identifiers.fieldName(metadataProperty), 
                        this.fieldDescriptor(metadataProperty)
                );
            }
        }
        
        for (MetadataProperty metadataProperty : this.getMetadataClass().getDeclaredProperties().values()) {
            String keyDescriptor = null;
            switch (metadataProperty.getPropertyType()) {
            case ASSOCIATION:
                if (metadataProperty.getKeyProperty() != null) {
                    keyDescriptor = metadataProperty.getKeyProperty().getDescriptor();
                } else {
                    keyDescriptor = metadataProperty.getKeyDescriptor();
                }
            case CONTRAVARIANCE:
                String targetDescriptor = metadataProperty.getTargetDescriptor();
                MetadataProperty associationProperty =  baseProperty(metadataProperty);
                String fieldDescriptor = this.fieldDescriptor(associationProperty);
                String fieldInternalName = this.fieldInternalName(associationProperty);
                String fieldName = Identifiers.fieldName(associationProperty);
                // TODO: 
                // Remove "&& ..." after "addKeyValidator" is added into "KeyedReference"
                if (keyDescriptor != null && associationProperty.getAssociationType() == AssociationType.MAP) {
                    mv.visitVarInsn(Opcodes.ALOAD, 0);
                    mv.visitFieldInsn(
                            Opcodes.GETFIELD, 
                            this.getInternalName(), 
                            fieldName, 
                            fieldDescriptor
                    );
                    ASMUtils.visitClassLdc(mv, keyDescriptor);
                    mv.visitMethodInsn(
                            Opcodes.INVOKESTATIC, 
                            ASMConstants.VALIDATORS_INTERNAL_NAME, 
                            "instanceOf", 
                            "(Ljava/lang/Class;)" + ASMConstants.VALIDATOR_DESCRIPTOR, 
                            false
                    );
                    mv.visitMethodInsn(
                            Opcodes.INVOKEVIRTUAL, 
                            fieldInternalName, 
                            "addKeyValidator", 
                            '(' + ASMConstants.VALIDATOR_DESCRIPTOR + ")V", 
                            false
                    );
                }
                mv.visitVarInsn(Opcodes.ALOAD, 0);
                mv.visitFieldInsn(
                        Opcodes.GETFIELD, 
                        this.getInternalName(), 
                        fieldName, 
                        fieldDescriptor
                );
                ASMUtils.visitClassLdc(mv, targetDescriptor);
                mv.visitMethodInsn(
                        Opcodes.INVOKESTATIC, 
                        ASMConstants.VALIDATORS_INTERNAL_NAME, 
                        "instanceOf", 
                        "(Ljava/lang/Class;)" + ASMConstants.VALIDATOR_DESCRIPTOR, 
                        false
                );
                mv.visitMethodInsn(
                        Opcodes.INVOKEVIRTUAL, 
                        fieldInternalName, 
                        associationProperty.getAssociationType() == AssociationType.MAP ? "addValueValidator" : "addValidator", 
                        '(' + ASMConstants.VALIDATOR_DESCRIPTOR + ")V", 
                        false
                );
                break;
            default:
                break;
            }
        }
        
        mv.visitMaxs(0, 0);
        mv.visitInsn(Opcodes.RETURN);
    }
    
    private void generatePropertyGetter(ClassVisitor cv, MetadataProperty metadataProperty) {
        
        MetadataProperty associationProperty = metadataProperty;
        
        MethodVisitor mv = cv.visitMethod(
                Opcodes.ACC_PUBLIC,
                Identifiers.getterName(metadataProperty),
                "()" + metadataProperty.getDescriptor(),
                metadataProperty.getSignature() != null ? "()" + metadataProperty.getSignature() : null,
                null
        );
        mv.visitCode();
        
        try (VariableScope scope = 
                new VariableScopeBuilder()
                .parameter("this", this.getDescriptor())
                .build(mv)
        ) {
            switch (metadataProperty.getPropertyType()) {
            case SCALAR:
                this.generateScalarGetterInsns(mv, metadataProperty);
                break;
            case INDEX:
                scope.load("this");
                mv.visitFieldInsn(
                        Opcodes.GETFIELD, 
                        this.getInternalName(), 
                        Identifiers.fieldName(metadataProperty.getReferenceProperty()), 
                        this.fieldDescriptor(metadataProperty.getReferenceProperty())
                );
                mv.visitInsn(metadataProperty.isAbsolute() ? Opcodes.ICONST_1 : Opcodes.ICONST_0);
                mv.visitMethodInsn(
                        Opcodes.INVOKEVIRTUAL, 
                        this.fieldInternalName(metadataProperty.getReferenceProperty()), 
                        "getIndex", 
                        "(Z)I",
                        false
                );
                mv.visitInsn(Opcodes.IRETURN);
                break;
            case KEY:
                scope.load("this");
                mv.visitFieldInsn(
                        Opcodes.GETFIELD, 
                        this.getInternalName(), 
                        Identifiers.fieldName(metadataProperty.getReferenceProperty()), 
                        this.fieldDescriptor(metadataProperty.getReferenceProperty())
                );
                mv.visitInsn(metadataProperty.isAbsolute() ? Opcodes.ICONST_1 : Opcodes.ICONST_0);
                mv.visitMethodInsn(
                        Opcodes.INVOKEVIRTUAL, 
                        this.fieldInternalName(metadataProperty.getReferenceProperty()), 
                        "getKey", 
                        "(Z)Ljava/lang/Object;",
                        false
                );
                mv.visitTypeInsn(Opcodes.CHECKCAST, ASMUtils.toInternalName(metadataProperty.getDescriptor()));
                mv.visitInsn(Opcodes.ARETURN);
                break;
            case CONTRAVARIANCE:
                associationProperty = baseProperty(metadataProperty);
            case ASSOCIATION:
                scope.load("this");
                String fieldInternalName = this.fieldInternalName(associationProperty);
                mv.visitFieldInsn(
                        Opcodes.GETFIELD, 
                        this.getInternalName(), 
                        Identifiers.fieldName(associationProperty), 
                        this.fieldDescriptor(associationProperty)
                );
                Class standardCollectionType = associationProperty.getStandardCollectionType();
                if (standardCollectionType == null) {
                    mv.visitMethodInsn(
                            Opcodes.INVOKEVIRTUAL, 
                            fieldInternalName, 
                            "get", 
                            "()Ljava/lang/Object;",
                            false
                    );
                    mv.visitTypeInsn(Opcodes.CHECKCAST, ASMUtils.toInternalName(metadataProperty.getDescriptor()));
                }
                mv.visitInsn(Opcodes.ARETURN);
                break;
            default:
                throw new AssertionError("Internal bug");
            }
        }
        
        mv.visitMaxs(0, 0);
        mv.visitEnd();
    }
    
    private void generateScalarGetterInsns(MethodVisitor mv, MetadataProperty metadataProperty) {
        
        if (!metadataProperty.isMandatory()) {
            Label enabledLabel = new Label();
            mv.visitVarInsn(Opcodes.ALOAD, 0);
            mv.visitFieldInsn(
                    Opcodes.GETFIELD, 
                    this.getInternalName(), 
                    Identifiers.stateFieldName(metadataProperty), 
                    "I"
            );
            mv.visitInsn(ASMConstants.OP_CONST_DISABLED);
            mv.visitInsn(Opcodes.IAND);
            mv.visitJumpInsn(Opcodes.IFEQ, enabledLabel);
            ASMUtils.visitClassLdc(mv, metadataProperty.getDeclaringClass().getDescriptor());
            mv.visitLdcInsn(metadataProperty.getId());
            mv.visitLdcInsn(metadataProperty.getName());
            mv.visitMethodInsn(
                    Opcodes.INVOKESTATIC, 
                    ASMConstants.OBJECT_MODEL_EXCEPTIONS_INTERNAL_NAME, 
                    "getDisabled", 
                    "(Ljava/lang/Class;ILjava/lang/String;)" + ASMConstants.RUNTIME_EXCEPTION_DESCRIPTOR, 
                    false
            );
            mv.visitInsn(Opcodes.ATHROW);
            mv.visitLabel(enabledLabel);
        }
        
        if (metadataProperty.isDeferrable()) {
            Label loadedLabel = new Label();
            
            mv.visitVarInsn(Opcodes.ALOAD, 0);
            mv.visitFieldInsn(
                    Opcodes.GETFIELD, 
                    ASMConstants.ABSTRACT_OBJECT_MODEL_IMPL_INTERNAL_NAME, 
                    "loading", 
                    "Z"
            );
            mv.visitJumpInsn(Opcodes.IFNE, loadedLabel);
            mv.visitVarInsn(Opcodes.ALOAD, 0);
            mv.visitFieldInsn(
                    Opcodes.GETFIELD, 
                    this.getInternalName(), 
                    Identifiers.stateFieldName(metadataProperty), 
                    "I"
            );
            mv.visitInsn(ASMConstants.OP_CONST_UNLOADED);
            mv.visitInsn(Opcodes.IAND);
            mv.visitJumpInsn(Opcodes.IFEQ, loadedLabel);
            
            mv.visitVarInsn(Opcodes.ALOAD, 0);
            mv.visitMethodInsn(
                    Opcodes.INVOKEVIRTUAL, 
                    this.getInternalName(), 
                    "loadScalars", 
                    "()V",
                    false
            );
            
            mv.visitLabel(loadedLabel);
        }
        
        mv.visitVarInsn(Opcodes.ALOAD, 0);
        mv.visitFieldInsn(
                Opcodes.GETFIELD, 
                this.getInternalName(), 
                metadataProperty.getName(), 
                metadataProperty.getDescriptor()
        );
        if (metadataProperty.getSimpleType() != null && (
                Date.class.isAssignableFrom(metadataProperty.getSimpleType()) || 
                Calendar.class.isAssignableFrom(metadataProperty.getSimpleType()))) {
            
            Label notNullLabel = new Label();
            mv.visitJumpInsn(Opcodes.IFNONNULL, notNullLabel);
            mv.visitInsn(Opcodes.ACONST_NULL);
            mv.visitInsn(Opcodes.ARETURN);
            mv.visitLabel(notNullLabel);
            
            mv.visitVarInsn(Opcodes.ALOAD, 0);
            mv.visitFieldInsn(
                    Opcodes.GETFIELD, 
                    this.getInternalName(), 
                    metadataProperty.getName(), 
                    metadataProperty.getDescriptor()
            );
            mv.visitMethodInsn(
                    Opcodes.INVOKEVIRTUAL, 
                    Type.getInternalName(metadataProperty.getSimpleType()), 
                    "clone", 
                    "()Ljava/lang/Object;", 
                    false
            );
            mv.visitTypeInsn(Opcodes.CHECKCAST, ASMUtils.toInternalName(metadataProperty.getDescriptor()));
        }
        mv.visitInsn(ASMUtils.getReturnCode(metadataProperty.getDescriptor()));
    }
    
    private void generatePropertySetter(ClassVisitor cv, MetadataProperty metadataProperty) {
        
        if (metadataProperty.getPropertyType() == PropertyType.SCALAR) {
            new ScalarSetterGenerator(this.getInternalName(), metadataProperty).generate(cv);
            return;
        }
        
        MetadataProperty associationProprty = metadataProperty;
        
        String setterSignature = metadataProperty.getSignature();
        if (setterSignature != null) {
            setterSignature = '(' + setterSignature + ")V";
        }
        MethodVisitor mv = cv.visitMethod(
                Opcodes.ACC_PUBLIC, 
                Identifiers.setterName(metadataProperty), 
                '(' + metadataProperty.getDescriptor() + ")V",
                setterSignature, 
                null
        );
        mv.visitCode();
        
        try (VariableScope scope = 
                new VariableScopeBuilder()
                .parameter("this", this.getDescriptor())
                .parameter("value", metadataProperty.getDescriptor(), metadataProperty.getSignature())
                .build(mv)) {
            
            switch (metadataProperty.getPropertyType()) {
            case INDEX:
                scope.load("this");
                mv.visitFieldInsn(
                        Opcodes.GETFIELD, 
                        this.getInternalName(), 
                        Identifiers.fieldName(metadataProperty.getReferenceProperty()), 
                        this.fieldDescriptor(metadataProperty.getReferenceProperty())
                );
                scope.load("value");
                mv.visitMethodInsn(
                        Opcodes.INVOKEVIRTUAL, 
                        this.fieldInternalName(metadataProperty.getReferenceProperty()), 
                        "setIndex", 
                        "(I)I",
                        false
                );
                mv.visitInsn(Opcodes.POP);
                break;
            case KEY:
                scope.load("this");
                mv.visitFieldInsn(
                        Opcodes.GETFIELD, 
                        this.getInternalName(), 
                        Identifiers.fieldName(metadataProperty.getReferenceProperty()), 
                        this.fieldDescriptor(metadataProperty.getReferenceProperty())
                );
                scope.load("value");
                mv.visitMethodInsn(
                        Opcodes.INVOKEVIRTUAL, 
                        this.fieldInternalName(metadataProperty.getReferenceProperty()), 
                        "setKey", 
                        "(Ljava/lang/Object;)Ljava/lang/Object;",
                        false
                );
                mv.visitInsn(Opcodes.POP);
                break;
            case CONTRAVARIANCE:
                associationProprty = baseProperty(metadataProperty);
            case ASSOCIATION:
                Label afterChangeLabel = new Label();
                scope.load("this");
                String fieldInternalName = this.fieldInternalName(associationProprty);
                mv.visitFieldInsn(
                        Opcodes.GETFIELD, 
                        this.getInternalName(), 
                        Identifiers.fieldName(associationProprty), 
                        this.fieldDescriptor(associationProprty)
                );
                Class standardCollectionType = associationProprty.getStandardCollectionType();
                if (standardCollectionType == null) {
                    scope.load("value");
                    mv.visitMethodInsn(
                            Opcodes.INVOKEVIRTUAL, 
                            fieldInternalName, 
                            "set", 
                            "(Ljava/lang/Object;)Ljava/lang/Object;",
                            false
                    );
                    mv.visitInsn(Opcodes.POP);
                } else if (Map.class.isAssignableFrom(standardCollectionType)) {
                    scope.declare("m", "Ljava/util/Map;");
                    scope.store("m");
                    scope.load("m");
                    mv.visitMethodInsn(
                            Opcodes.INVOKEVIRTUAL, 
                            fieldInternalName, 
                            "clear", 
                            "()V", 
                            false
                    );
                    scope.load("value");
                    mv.visitJumpInsn(Opcodes.IFNULL, afterChangeLabel);
                    scope.load("m");
                    scope.load("value");
                    mv.visitMethodInsn(
                            Opcodes.INVOKEVIRTUAL, 
                            fieldInternalName, 
                            "putAll", 
                            "(Ljava/util/Map;)Z", 
                            false
                    );
                    mv.visitInsn(Opcodes.POP);
                    mv.visitLabel(afterChangeLabel);
                } else {
                    scope.declare("c", "Ljava/util/Collection;");
                    scope.store("c");
                    scope.load("c");
                    mv.visitMethodInsn(
                            Opcodes.INVOKEVIRTUAL, 
                            fieldInternalName, 
                            "clear", 
                            "()V", 
                            false
                    );
                    scope.load("value");
                    mv.visitJumpInsn(Opcodes.IFNULL, afterChangeLabel);
                    scope.load("c");
                    scope.load("value");
                    mv.visitMethodInsn(
                            Opcodes.INVOKEVIRTUAL, 
                            fieldInternalName, 
                            "addAll", 
                            "(Ljava/util/Collection;)Z", 
                            false
                    );
                    mv.visitInsn(Opcodes.POP);
                    mv.visitLabel(afterChangeLabel);
                }
                break;
            default:
                throw new AssertionError("Internal bug");
            }
        }
        
        mv.visitInsn(Opcodes.RETURN);
        
        mv.visitMaxs(0, 0);
        mv.visitEnd();
    }
    
    private void generateObjectOperationMethods(ClassVisitor cv, boolean getter) {
        
        List propertyList = this.getMetadataClass().getPropertyList();
        
        MethodVisitor mv = cv.visitMethod(
                Opcodes.ACC_PUBLIC, 
                getter ? "get" : "set", 
                getter ? "(I)Ljava/lang/Object;" : "(ILjava/lang/Object;)V", 
                null,
                null
        );
        mv.visitCode();
        
        VariableScopeBuilder variableScopeBuilder = 
                new VariableScopeBuilder()
                .parameter("this", this.getDescriptor())
                .parameter("propertyId", "I");
        if (!getter) {
            variableScopeBuilder.parameter("value", "Ljava/lang/Object;");
        }
        try (VariableScope scope = variableScopeBuilder.build(mv)) {
            if (!propertyList.isEmpty()) {
                Label defaultLabel = new Label();
                Label[] labels = new Label[propertyList.size()];
                for (int i = labels.length - 1; i >= 0; i--) {
                    labels[i] = new Label();
                }
                
                mv.visitVarInsn(Opcodes.ILOAD, 1);
                mv.visitTableSwitchInsn(// It's very important to use table-switch, not lookup-switch!!!
                        0, 
                        propertyList.size() - 1, 
                        defaultLabel, 
                        labels
                );
                
                for (final MetadataProperty metadataProperty : propertyList) {
                    
                    mv.visitLabel(labels[metadataProperty.getId()]);
                    
                    if (getter) {
                        ASMUtils.visitBox(mv, metadataProperty.getDescriptor(), v -> {
                            v.visitVarInsn(Opcodes.ALOAD, 0);
                            v.visitMethodInsn(
                                    Opcodes.INVOKEVIRTUAL, 
                                    ObjectModelTargetGenerator.this.getInternalName(), 
                                    Identifiers.getterName(metadataProperty), 
                                    "()" + metadataProperty.getDescriptor(),
                                    false
                            );
                        });
                        mv.visitInsn(Opcodes.ARETURN);
                    } else {
                        Label nullLabel = new Label();
                        Label endIfLabel = new Label();
                        scope.load("this");
                            if (metadataProperty.getSimpleType() != null && metadataProperty.getSimpleType().isPrimitive()) {
                            scope.load("value");
                            mv.visitJumpInsn(Opcodes.IFNULL, nullLabel);
                            scope.load("value");
                            ASMUtils.visitUnbox(mv, metadataProperty.getDescriptor(), true);
                            mv.visitJumpInsn(Opcodes.GOTO, endIfLabel);
                            mv.visitLabel(nullLabel);
                            mv.visitInsn(ASMUtils.getDefaultCode(metadataProperty.getDescriptor()));
                            mv.visitLabel(endIfLabel);
                        } else {
                            scope.load("value");
                            ASMUtils.visitUnbox(mv, metadataProperty.getDescriptor(), true);
                        }
                        mv.visitMethodInsn(
                                Opcodes.INVOKEVIRTUAL, 
                                ObjectModelTargetGenerator.this.getInternalName(), 
                                Identifiers.setterName(metadataProperty), 
                                '(' + metadataProperty.getDescriptor() + ")V",
                                false
                        );
                        mv.visitInsn(Opcodes.RETURN);
                    }
                }
                
                mv.visitLabel(defaultLabel);
            }
            
            ASMUtils.visitClassLdc(mv, this.getMetadataClass().getDescriptor());
            mv.visitVarInsn(Opcodes.ILOAD, 1);
            mv.visitMethodInsn(
                    Opcodes.INVOKESTATIC, 
                    ASMConstants.OBJECT_MODEL_EXCEPTIONS_INTERNAL_NAME, 
                    getter ? "getNonExisting" : "setNonExisting", 
                    "(Ljava/lang/Class;I)" + ASMConstants.RUNTIME_EXCEPTION_DESCRIPTOR, 
                    false
            );
            mv.visitInsn(Opcodes.ATHROW);
        }
        
        mv.visitMaxs(0, 0);
        mv.visitEnd();
    }
    
    private void generateGetAssociatedEndpointMethod(ClassVisitor cv) {
        
        List propertyList = this.getMetadataClass().getPropertyList();    
        
        MethodVisitor mv = cv.visitMethod(
                Opcodes.ACC_PUBLIC, 
                "getAssociatedEndpoint", 
                "(I)" + ASMConstants.ASSOCIATED_ENDPOINT_DESCRIPTOR, 
                null,
                null
        );
        mv.visitCode();
        
        try (VariableScope scope = 
                new VariableScopeBuilder()
                .parameter("this", this.getDescriptor())
                .parameter("associationPropertyId", "I")
                .build(mv)
        ) {
            
            if (!propertyList.isEmpty()) {
                Label defaultLabel = new Label();
                Label[] labels = new Label[propertyList.size()];
                for (int i = labels.length - 1; i >= 0; i--) {
                    labels[i] = new Label();
                }
                
                scope.load("associationPropertyId");
                mv.visitTableSwitchInsn(// It's very important to use table-switch, not lookup-switch!!!
                        0, 
                        propertyList.size() - 1, 
                        defaultLabel, 
                        labels
                );
                
                for (MetadataProperty metadataProperty : propertyList) {
                    
                    mv.visitLabel(labels[metadataProperty.getId()]);
                    metadataProperty = baseProperty(metadataProperty);
                    
                    switch (metadataProperty.getPropertyType()) {
                    case SCALAR:
                        ASMUtils.visitClassLdc(mv, this.getMetadataClass().getDescriptor());
                        mv.visitLdcInsn(metadataProperty.getId());
                        mv.visitLdcInsn(metadataProperty.getName());
                        mv.visitMethodInsn(
                                Opcodes.INVOKESTATIC, 
                                ASMConstants.OBJECT_MODEL_EXCEPTIONS_INTERNAL_NAME, 
                                "getNonAssociation", 
                                "(Ljava/lang/Class;ILjava/lang/String;)" + ASMConstants.RUNTIME_EXCEPTION_DESCRIPTOR, 
                                false
                        );
                        mv.visitInsn(Opcodes.ATHROW);
                        break;
                    case ASSOCIATION:
                        scope.load("this");
                        mv.visitFieldInsn(
                                Opcodes.GETFIELD, 
                                this.getInternalName(), 
                                Identifiers.fieldName(metadataProperty), 
                                this.fieldDescriptor(metadataProperty)
                        );
                        mv.visitInsn(Opcodes.ARETURN);
                        break;
                    default:
                        throw new AssertionError("Internal bug");
                    }
                }
                
                mv.visitLabel(defaultLabel);
            }
            
            ASMUtils.visitClassLdc(mv, this.getMetadataClass().getDescriptor());
            mv.visitVarInsn(Opcodes.ILOAD, 1);
            mv.visitMethodInsn(
                    Opcodes.INVOKESTATIC, 
                    ASMConstants.OBJECT_MODEL_EXCEPTIONS_INTERNAL_NAME, 
                    "getNonExistingAsAssociation", 
                    "(Ljava/lang/Class;I)" + ASMConstants.RUNTIME_EXCEPTION_DESCRIPTOR, 
                    false
            );
            mv.visitInsn(Opcodes.ATHROW);
        }
        
        mv.visitMaxs(0, 0);
        mv.visitEnd();
    }
    
    private void generateGetDisablityPropertyMethod(ClassVisitor cv) {
        
        List propertyList = this.getMetadataClass().getPropertyList();    
        
        MethodVisitor mv = cv.visitMethod(
                Opcodes.ACC_PUBLIC, 
                "isDisabled", 
                "(I)Z", 
                null,
                null
        );
        mv.visitCode();
        
        try (VariableScope scope = 
                new VariableScopeBuilder()
                .parameter("this", this.getDescriptor())
                .parameter("propertyId", "I")
                .build(mv)
        ) {
            
            if (!propertyList.isEmpty()) {
                Label defaultLabel = new Label();
                Label[] labels = new Label[propertyList.size()];
                for (int i = labels.length - 1; i >= 0; i--) {
                    labels[i] = new Label();
                }
                
                scope.load("propertyId");
                mv.visitTableSwitchInsn(// It's very important to use table-switch, not lookup-switch!!!
                        0, 
                        propertyList.size() - 1, 
                        defaultLabel, 
                        labels
                );
                
                for (MetadataProperty metadataProperty : propertyList) {
                    
                    mv.visitLabel(labels[metadataProperty.getId()]);
                    metadataProperty = baseProperty(metadataProperty);
                    
                    if (metadataProperty.getPropertyType() == PropertyType.SCALAR) {
                        String stateFieldName = Identifiers.stateFieldName(metadataProperty);
                        if (stateFieldName != null && !metadataProperty.isMandatory()) {
                            Label falseLabel = new Label();
                            scope.load("this");
                            mv.visitFieldInsn(
                                    Opcodes.GETFIELD, 
                                    this.getInternalName(), 
                                    stateFieldName, 
                                    "I"
                            );
                            mv.visitInsn(ASMConstants.OP_CONST_DISABLED);
                            mv.visitInsn(Opcodes.IAND);
                            mv.visitJumpInsn(Opcodes.IFEQ, falseLabel);
                            mv.visitInsn(Opcodes.ICONST_1);
                            mv.visitInsn(Opcodes.IRETURN);
                            mv.visitLabel(falseLabel);
                        }
                        mv.visitInsn(Opcodes.ICONST_0);
                        mv.visitInsn(Opcodes.IRETURN);
                    } else {
                        mv.visitVarInsn(Opcodes.ALOAD, 0);
                        mv.visitFieldInsn(
                                Opcodes.GETFIELD, 
                                this.getInternalName(), 
                                Identifiers.fieldName(metadataProperty), 
                                this.fieldDescriptor(metadataProperty)
                        );
                        mv.visitMethodInsn(
                                Opcodes.INVOKEVIRTUAL, 
                                this.fieldInternalName(metadataProperty), 
                                "isDisabled", 
                                "()Z", 
                                false
                        );
                        mv.visitInsn(Opcodes.IRETURN);
                    }
                }
                
                mv.visitLabel(defaultLabel);
            }
            
            ASMUtils.visitClassLdc(mv, this.getMetadataClass().getDescriptor());
            mv.visitVarInsn(Opcodes.ILOAD, 1);
            mv.visitMethodInsn(
                    Opcodes.INVOKESTATIC, 
                    ASMConstants.OBJECT_MODEL_EXCEPTIONS_INTERNAL_NAME, 
                    "checkDisablityForNonExisting", 
                    "(Ljava/lang/Class;I)" + ASMConstants.RUNTIME_EXCEPTION_DESCRIPTOR, 
                    false
            );
            mv.visitInsn(Opcodes.ATHROW);
        }
        
        mv.visitMaxs(0, 0);
        mv.visitEnd();
    }
    
    private void generateSetDisablityPropertyMethod(ClassVisitor cv, boolean enable) {
        
        List propertyList = this.getMetadataClass().getPropertyList();    
        
        MethodVisitor mv = cv.visitMethod(
                Opcodes.ACC_PUBLIC, 
                enable ? "enable" : "disable", 
                "(I)V", 
                null,
                null
        );
        mv.visitCode();
        
        try (VariableScope scope = 
                new VariableScopeBuilder()
                .parameter("this", this.getDescriptor())
                .parameter("propertyId", "I")
                .build(mv)
        ) {
            
            if (!propertyList.isEmpty()) {
                Label defaultLabel = new Label();
                Label[] labels = new Label[propertyList.size()];
                for (int i = labels.length - 1; i >= 0; i--) {
                    labels[i] = new Label();
                }
                
                mv.visitVarInsn(Opcodes.ILOAD, 1);
                mv.visitTableSwitchInsn(// It's very important to use table-switch, not lookup-switch!!!
                        0, 
                        propertyList.size() - 1, 
                        defaultLabel, 
                        labels
                );
                
                for (MetadataProperty metadataProperty : propertyList) {
                    
                    mv.visitLabel(labels[metadataProperty.getId()]);
                    metadataProperty = baseProperty(metadataProperty);
                    
                    if (metadataProperty.getPropertyType() == PropertyType.SCALAR) {
                        String stateFieldName = Identifiers.stateFieldName(metadataProperty);
                        if (stateFieldName != null && !metadataProperty.isMandatory()) {
                            mv.visitVarInsn(Opcodes.ALOAD, 0);
                            mv.visitVarInsn(Opcodes.ALOAD, 0);
                            mv.visitFieldInsn(
                                    Opcodes.GETFIELD, 
                                    this.getInternalName(), 
                                    stateFieldName, 
                                    "I"
                            );
                            if (enable) {
                                mv.visitLdcInsn(ASMConstants.LDC_CONST_CLEAR_DISABLED);
                                mv.visitInsn(Opcodes.IAND);
                            } else {
                                mv.visitInsn(ASMConstants.OP_CONST_DISABLED);
                                mv.visitInsn(Opcodes.IOR);
                            }
                            mv.visitFieldInsn(
                                    Opcodes.PUTFIELD, 
                                    this.getInternalName(), 
                                    stateFieldName, 
                                    "I"
                            );
                        }
                    } else {
                        mv.visitVarInsn(Opcodes.ALOAD, 0);
                        mv.visitFieldInsn(
                                Opcodes.GETFIELD, 
                                this.getInternalName(), 
                                Identifiers.fieldName(metadataProperty), 
                                this.fieldDescriptor(metadataProperty)
                        );
                        mv.visitMethodInsn(
                                Opcodes.INVOKEVIRTUAL, 
                                this.fieldInternalName(metadataProperty), 
                                enable ? "enable" : "disable", 
                                "()V", 
                                false
                        );
                    }
                    mv.visitInsn(Opcodes.RETURN);
                }
                
                mv.visitLabel(defaultLabel);
            }
            
            ASMUtils.visitClassLdc(mv, this.getMetadataClass().getDescriptor());
            mv.visitVarInsn(Opcodes.ILOAD, 1);
            mv.visitMethodInsn(
                    Opcodes.INVOKESTATIC, 
                    ASMConstants.OBJECT_MODEL_EXCEPTIONS_INTERNAL_NAME, 
                    enable ? "enableNonExisting" : "disableNonExisting", 
                    "(Ljava/lang/Class;I)" + ASMConstants.RUNTIME_EXCEPTION_DESCRIPTOR, 
                    false
            );
            mv.visitInsn(Opcodes.ATHROW);
        }
        
        mv.visitMaxs(0, 0);
        mv.visitEnd();
    }
    
    private void generateIsLoadedMethod(ClassVisitor cv) {
        List propertyList = this.getMetadataClass().getPropertyList();    
        
        MethodVisitor mv = cv.visitMethod(
                Opcodes.ACC_PUBLIC, 
                "isLoaded", 
                "(I)Z", 
                null,
                null
        );
        
        mv.visitCode();
        
        try (VariableScope scope = 
                new VariableScopeBuilder()
                .parameter("this", this.getDescriptor())
                .parameter("propertyId", "I")
                .build(mv)) {
            
            if (!propertyList.isEmpty()) {
                Label defaultLabel = new Label();
                Label[] labels = new Label[propertyList.size()];
                for (int i = labels.length - 1; i >= 0; i--) {
                    labels[i] = new Label();
                }
                
                scope.load("propertyId");
                mv.visitTableSwitchInsn(// It's very important to use table-switch, not lookup-switch!!!
                        0, 
                        propertyList.size() - 1, 
                        defaultLabel, 
                        labels
                );
                for (MetadataProperty metadataProperty : propertyList) {
                    
                    mv.visitLabel(labels[metadataProperty.getId()]);
                    metadataProperty = baseProperty(metadataProperty);
                    
                    switch (metadataProperty.getPropertyType()) {
                    case SCALAR:
                        if (!metadataProperty.isDeferrable()) {
                            mv.visitInsn(Opcodes.ICONST_1);
                            mv.visitInsn(Opcodes.IRETURN);
                        } else {
                            Label unloadedLabel = new Label();
                            String stateFieldName = Identifiers.stateFieldName(metadataProperty);
                            scope.load("this");
                            mv.visitFieldInsn(
                                    Opcodes.GETFIELD, 
                                    this.getInternalName(), 
                                    stateFieldName, 
                                    "I"
                            );
                            mv.visitInsn(ASMConstants.OP_CONST_UNLOADED);
                            mv.visitInsn(Opcodes.IAND);
                            mv.visitJumpInsn(Opcodes.IFNE, unloadedLabel);
                            mv.visitInsn(Opcodes.ICONST_1);
                            mv.visitInsn(Opcodes.IRETURN);
                            mv.visitLabel(unloadedLabel);
                            mv.visitInsn(Opcodes.ICONST_0);
                            mv.visitInsn(Opcodes.IRETURN);
                        }
                        break;
                    case ASSOCIATION:
                        scope.load("this");
                        mv.visitFieldInsn(
                                Opcodes.GETFIELD, 
                                this.getInternalName(), 
                                Identifiers.fieldName(metadataProperty), 
                                this.fieldDescriptor(metadataProperty)
                        );
                        mv.visitMethodInsn(
                                Opcodes.INVOKEVIRTUAL, 
                                this.fieldInternalName(metadataProperty), 
                                "isLoaded", 
                                "()Z", 
                                false
                        );
                        mv.visitInsn(Opcodes.IRETURN);
                        break;
                    default:
                        throw new AssertionError("Internal bug");
                    }
                }
                
                mv.visitLabel(defaultLabel);
            }
            
            ASMUtils.visitClassLdc(mv, this.getMetadataClass().getDescriptor());
            scope.load("propertyId");
            mv.visitMethodInsn(
                    Opcodes.INVOKESTATIC, 
                    ASMConstants.OBJECT_MODEL_EXCEPTIONS_INTERNAL_NAME, 
                    "checkLazinessForNonExisting", 
                    "(Ljava/lang/Class;I)" + ASMConstants.RUNTIME_EXCEPTION_DESCRIPTOR, 
                    false
            );
            mv.visitInsn(Opcodes.ATHROW);
        }
        
        mv.visitMaxs(0, 0);
        mv.visitEnd();
    }
    
    private void generateUnloadMethod(ClassVisitor cv) {
        List propertyList = this.getMetadataClass().getPropertyList();    
        
        try (ScopedMethodVisitor mv = 
                new ScopedMethodVisitorBuilder(Opcodes.ACC_PUBLIC, "unload")
                .self(this.getDescriptor())
                .parameter("propertyId", "I")
                .build(cv)) {
            
            mv.visitCode();
            
            if (!propertyList.isEmpty()) {
                Label defaultLabel = new Label();
                Label[] labels = new Label[propertyList.size()];
                for (int i = labels.length - 1; i >= 0; i--) {
                    labels[i] = new Label();
                }
                
                mv.load("propertyId");
                mv.visitTableSwitchInsn(// It's very important to use table-switch, not lookup-switch!!!
                        0, 
                        propertyList.size() - 1, 
                        defaultLabel, 
                        labels
                );
                for (MetadataProperty metadataProperty : propertyList) {
                    
                    mv.visitLabel(labels[metadataProperty.getId()]);
                    
                    if (metadataProperty.isDeferrable() && metadataProperty.getPropertyType() == PropertyType.SCALAR) {
                        String frozenContextFieldName = Identifiers.frozenContextFieldName(metadataProperty);
                        if (frozenContextFieldName != null) {
                            Label frozenIsNullLabel = new Label();
                            mv.load("this");
                            mv.visitFieldInsn(
                                    Opcodes.GETFIELD, 
                                    this.getInternalName(), 
                                    frozenContextFieldName, 
                                    ASMConstants.FROZEN_CONTEXT_DESCRIPTOR
                            );
                            mv.visitJumpInsn(Opcodes.IFNULL, frozenIsNullLabel);
                            ASMUtils.visitClassLdc(mv, this.getMetadataClass().getDescriptor());
                            mv.visitLdcInsn(metadataProperty.getId());
                            mv.visitLdcInsn(metadataProperty.getName());
                            mv.visitMethodInsn(
                                    Opcodes.INVOKESTATIC, 
                                    ASMConstants.OBJECT_MODEL_EXCEPTIONS_INTERNAL_NAME, 
                                    "unloadFrozen", 
                                    "(Ljava/lang/Class;ILjava/lang/String;)" + ASMConstants.RUNTIME_EXCEPTION_DESCRIPTOR, 
                                    false
                            );
                            mv.visitInsn(Opcodes.ATHROW);
                            mv.visitLabel(frozenIsNullLabel);
                        }
                        String stateFieldName = Identifiers.stateFieldName(metadataProperty);
                        mv.load("this");
                        mv.load("this");
                        mv.visitFieldInsn(
                                Opcodes.GETFIELD, 
                                this.getInternalName(), 
                                stateFieldName, 
                                "I"
                        );
                        mv.visitInsn(ASMConstants.OP_CONST_UNLOADED);
                        mv.visitInsn(Opcodes.IOR);
                        mv.visitFieldInsn(
                                Opcodes.PUTFIELD, 
                                this.getInternalName(), 
                                stateFieldName, 
                                "I"
                        );
                        mv.load("this");
                        mv.visitInsn(ASMUtils.getDefaultCode(metadataProperty.getDescriptor()));
                        mv.visitFieldInsn(
                                Opcodes.PUTFIELD, 
                                this.getInternalName(), 
                                metadataProperty.getName(), 
                                metadataProperty.getDescriptor()
                        );
                    }
                    mv.visitInsn(Opcodes.RETURN);
                }
                
                mv.visitLabel(defaultLabel);
            }
            
            ASMUtils.visitClassLdc(mv, this.getMetadataClass().getDescriptor());
            mv.load("propertyId");
            mv.visitMethodInsn(
                    Opcodes.INVOKESTATIC, 
                    ASMConstants.OBJECT_MODEL_EXCEPTIONS_INTERNAL_NAME, 
                    "unloadNonExisting", 
                    "(Ljava/lang/Class;I)" + ASMConstants.RUNTIME_EXCEPTION_DESCRIPTOR, 
                    false
            );
            mv.visitInsn(Opcodes.ATHROW);
        
            mv.visitMaxs(0, 0);
            mv.visitEnd();
        }
    }
    
    private void generateLoadScalars(ClassVisitor cv) {
        if (!this.overrideLoadScalars) {
            return;
        }
        
        MethodVisitor mv = cv.visitMethod(Opcodes.ACC_PUBLIC, "loadScalars", "()V", null, null);
        mv.visitCode();
        mv.visitVarInsn(Opcodes.ALOAD, 0);
        mv.visitFieldInsn(
                Opcodes.GETSTATIC, 
                this.getInternalName(),
                Identifiers.DEFERRABLE_SCALAR_PROPERTY_IDS,
                "[I"
        );
        mv.visitMethodInsn(
                Opcodes.INVOKEVIRTUAL, 
                ASMConstants.ABSTRACT_OBJECT_MODEL_IMPL_INTERNAL_NAME, 
                "load", 
                "([I)V", 
                false
        );
        mv.visitInsn(Opcodes.RETURN);
        mv.visitMaxs(0, 0);
        mv.visitEnd();
    }
    
    private void generateScalarFrozenContextOperationMethods(ClassVisitor cv, boolean freeze) {
        
        List propertyList = this.getMetadataClass().getPropertyList();
        
        MethodVisitor mv = cv.visitMethod(
                Opcodes.ACC_PUBLIC, 
                freeze ? "freezeScalar" : "unfreezeScalar", 
                "(I" + ASMConstants.FROZEN_CONTEXT_DESCRIPTOR + ")V", 
                "(IL" + ASMConstants.FROZEN_CONTEXT_INTERNAL_NAME + "<*>;)V",
                null
        );
        mv.visitCode();
        
        try (VariableScope scope = 
                new VariableScopeBuilder()
                .parameter("this", this.getDescriptor())
                .parameter("scalarPropertyId", "I")
                .parameter("ctx", ASMConstants.FROZEN_CONTEXT_DESCRIPTOR, 'L' + ASMConstants.FROZEN_CONTEXT_INTERNAL_NAME + "<*>;")
                .build(mv)
        ) {
            if (!propertyList.isEmpty()) {
                Label defaultLabel = new Label();
                Label[] labels = new Label[propertyList.size()];
                for (int i = labels.length - 1; i >= 0; i--) {
                    labels[i] = new Label();
                }
                
                scope.load("scalarPropertyId");
                mv.visitTableSwitchInsn(// It's very important to use table-switch, not lookup-switch!!!
                        0, 
                        propertyList.size() - 1, 
                        defaultLabel, 
                        labels
                );
                
                for (MetadataProperty metadataProperty : propertyList) {
                    
                    mv.visitLabel(labels[metadataProperty.getId()]);
                    
                    if (metadataProperty.getPropertyType() != PropertyType.SCALAR) {
                        ASMUtils.visitClassLdc(mv, metadataProperty.getDeclaringClass().getDescriptor());
                        mv.visitLdcInsn(metadataProperty.getId());
                        mv.visitLdcInsn(metadataProperty.getName());
                        mv.visitMethodInsn(
                                Opcodes.INVOKESTATIC, 
                                ASMConstants.OBJECT_MODEL_EXCEPTIONS_INTERNAL_NAME, 
                                freeze ? "freezeNonScalar" : "unfreezeNonScalar", 
                                "(Ljava/lang/Class;ILjava/lang/String;)" + ASMConstants.RUNTIME_EXCEPTION_DESCRIPTOR, 
                                false
                        );
                        mv.visitInsn(Opcodes.ATHROW);
                    } else if (metadataProperty.getDescriptor().charAt(0) == '[') {
                        ASMUtils.visitClassLdc(mv, metadataProperty.getDeclaringClass().getDescriptor());
                        mv.visitLdcInsn(metadataProperty.getId());
                        mv.visitLdcInsn(metadataProperty.getName());
                        mv.visitMethodInsn(
                                Opcodes.INVOKESTATIC, 
                                ASMConstants.OBJECT_MODEL_EXCEPTIONS_INTERNAL_NAME, 
                                freeze ? "freezeArray" : "unfreezeArray", 
                                "(Ljava/lang/Class;ILjava/lang/String;)" + ASMConstants.RUNTIME_EXCEPTION_DESCRIPTOR, 
                                false
                        );
                        mv.visitInsn(Opcodes.ATHROW);
                    } else {
                        String frozenContextFieldName = Identifiers.frozenContextFieldName(metadataProperty);
                        scope.load("this");
                        scope.load("this");
                        mv.visitFieldInsn(
                                Opcodes.GETFIELD, 
                                this.getInternalName(), 
                                frozenContextFieldName, 
                                ASMConstants.FROZEN_CONTEXT_DESCRIPTOR
                        );
                        scope.load("ctx");
                        mv.visitMethodInsn(
                                Opcodes.INVOKESTATIC, 
                                ASMConstants.FROZEN_CONTEXT_INTERNAL_NAME, 
                                freeze ? "combine" : "remove", 
                                '(' + 
                                ASMConstants.FROZEN_CONTEXT_DESCRIPTOR + 
                                ASMConstants.FROZEN_CONTEXT_DESCRIPTOR + 
                                ')' + 
                                ASMConstants.FROZEN_CONTEXT_DESCRIPTOR, 
                                false
                        );
                        mv.visitFieldInsn(
                                Opcodes.PUTFIELD, 
                                this.getInternalName(), 
                                frozenContextFieldName, 
                                ASMConstants.FROZEN_CONTEXT_DESCRIPTOR
                        );
                        mv.visitInsn(Opcodes.RETURN);
                    }
                }
                
                mv.visitLabel(defaultLabel);
            }
            
            ASMUtils.visitClassLdc(mv, this.getMetadataClass().getDescriptor());
            scope.load("scalarPropertyId");
            mv.visitMethodInsn(
                    Opcodes.INVOKESTATIC, 
                    ASMConstants.OBJECT_MODEL_EXCEPTIONS_INTERNAL_NAME, 
                    freeze ? "freezeNonExisting" : "unfreezeNonExisting", 
                    "(Ljava/lang/Class;I)" + ASMConstants.RUNTIME_EXCEPTION_DESCRIPTOR, 
                    false
            );
            mv.visitInsn(Opcodes.ATHROW);
        }
        
        mv.visitMaxs(0, 0);
        mv.visitEnd();
    }
    
    private void generateGetEmbeddedFrozenContextMethod(ClassVisitor cv) {
        
        List propertyList = this.getMetadataClass().getPropertyList();
        
        MethodVisitor mv = cv.visitMethod(
                Opcodes.ACC_PROTECTED, 
                "getEmbeddedFrozenContext", 
                "(I)" + ASMConstants.FROZEN_CONTEXT_DESCRIPTOR, 
                "(I)L" + ASMConstants.FROZEN_CONTEXT_INTERNAL_NAME + "<*>;",
                null
        );
        mv.visitCode();
        
        try (VariableScope scope = 
                new VariableScopeBuilder()
                .parameter("this", this.getDescriptor())
                .parameter("scalarPropertyId", "I")
                .build(mv)
        ) {
            int minEmbeddedPropertyId = -1;
            int maxEmbeddedPropertyId = propertyList.size();
            while (++minEmbeddedPropertyId < maxEmbeddedPropertyId) {
                MetadataProperty property = propertyList.get(minEmbeddedPropertyId);
                if (property.getPropertyType() == PropertyType.SCALAR && property.getTargetClass() != null) {
                    break;
                }
            }
            while (--maxEmbeddedPropertyId >= 0) {
                MetadataProperty property = propertyList.get(maxEmbeddedPropertyId);
                if (property.getPropertyType() == PropertyType.SCALAR && property.getTargetClass() != null) {
                    break;
                }
            }
            if (minEmbeddedPropertyId <= maxEmbeddedPropertyId) {
                Label defaultLabel = new Label();
                Label[] labels = new Label[maxEmbeddedPropertyId - minEmbeddedPropertyId + 1];
                for (int i = labels.length - 1; i >= 0; i--) {
                    labels[i] = new Label();
                }
                
                scope.load("scalarPropertyId");
                mv.visitTableSwitchInsn(// It's very important to use table-switch, not lookup-switch!!!
                        minEmbeddedPropertyId, 
                        maxEmbeddedPropertyId, 
                        defaultLabel, 
                        labels
                );
                
                for (int propertyId = minEmbeddedPropertyId; propertyId <= maxEmbeddedPropertyId; propertyId++) {
                    MetadataProperty metadataProperty = propertyList.get(propertyId);
                    mv.visitLabel(labels[propertyId - minEmbeddedPropertyId]);
                    
                    if (metadataProperty.getPropertyType() != PropertyType.SCALAR || 
                            metadataProperty.getTargetClass() == null) {
                        MetadataProperty nextProperty = propertyList.get(propertyId + 1);
                        if (nextProperty.getPropertyType() == PropertyType.SCALAR && nextProperty.getTargetClass() != null) {
                            mv.visitInsn(Opcodes.ACONST_NULL);
                            mv.visitInsn(Opcodes.ARETURN);
                        }
                    } else {
                        scope.load("this");
                        mv.visitFieldInsn(
                                Opcodes.GETFIELD, 
                                this.getInternalName(), 
                                Identifiers.frozenContextFieldName(metadataProperty), 
                                ASMConstants.FROZEN_CONTEXT_DESCRIPTOR
                        );
                        mv.visitInsn(Opcodes.ARETURN);
                    }
                }
                
                mv.visitLabel(defaultLabel);
            }
            mv.visitInsn(Opcodes.ACONST_NULL);
            mv.visitInsn(Opcodes.ARETURN);
        }
        
        mv.visitMaxs(0, 0);
        mv.visitEnd();
    }
    
    private void generateWriteObject(ClassVisitor cv) {
        try (ScopedMethodVisitor mv =
                new ScopedMethodVisitorBuilder(
                        Opcodes.ACC_PRIVATE, 
                        "writeObject", 
                        ASMConstants.IO_EXCEPTION_INTERNAL_NAME)
                .self(this.getDescriptor())
                .parameter("out", ASMConstants.OBJECT_OUTPUT_STREAM_DESCRIPTOR)
                .build(cv)) {
            
            mv.visitCode();
            
            for (MetadataProperty metadataProperty : this.getMetadataClass().getDeclaredProperties().values()) {
                if (metadataProperty.getPropertyType() != PropertyType.SCALAR) {
                    continue;
                }
                String stateFieldName = Identifiers.stateFieldName(metadataProperty);
                if (stateFieldName != null) {
                    mv.load("out");
                    mv.load("this");
                    mv.visitFieldInsn(
                            Opcodes.GETFIELD, 
                            this.getInternalName(), 
                            stateFieldName, 
                            "I"
                    );
                    mv.visitMethodInsn(
                            Opcodes.INVOKEVIRTUAL, 
                            ASMConstants.OBJECT_OUTPUT_STREAM_INTERNAL_NAME, 
                            "writeInt", 
                            "(I)V", 
                            false
                    );
                }
                mv.load("out");
                mv.load("this");
                mv.visitFieldInsn(
                        Opcodes.GETFIELD, 
                        this.getInternalName(), 
                        Identifiers.fieldName(metadataProperty), 
                        this.fieldDescriptor(metadataProperty)
                );
                visitIOMethodInvocation(mv, metadataProperty, false);
            }
            for (MetadataProperty metadataProperty : this.getMetadataClass().getDeclaredProperties().values()) {
                if (metadataProperty.getPropertyType() != PropertyType.ASSOCIATION) {
                    continue;
                }
                mv.load("out");
                mv.load("this");
                mv.visitFieldInsn(
                        Opcodes.GETFIELD, 
                        this.getInternalName(), 
                        Identifiers.fieldName(metadataProperty), 
                        this.fieldDescriptor(metadataProperty)
                );
                visitIOMethodInvocation(mv, metadataProperty, false);
            }
            mv.visitInsn(Opcodes.RETURN);
            
            mv.visitMaxs(0, 0);
            mv.visitEnd();
        }
    }
    
    private void generateReadObject(ClassVisitor cv) {
        try (ScopedMethodVisitor mv =
                new ScopedMethodVisitorBuilder(
                        Opcodes.ACC_PRIVATE, 
                        "readObject", 
                        ASMConstants.IO_EXCEPTION_INTERNAL_NAME,
                        ASMConstants.CLASS_NOT_FOUND_EXCEPTION_INTERNAL_NAME)
                .self(this.getDescriptor())
                .parameter("in", ASMConstants.OBJECT_INPUT_STREAM_DESCRIPTOR)
                .build(cv)) {
            
            mv.visitCode();
            
            if (this.getMetadataClass().getSuperClass() == null) {
                mv.load("this");
                mv.visitFieldInsn(
                        Opcodes.GETFIELD, 
                        ASMConstants.ABSTRACT_OBJECT_MODEL_IMPL_INTERNAL_NAME, 
                        "owner", 
                        "Ljava/lang/Object;"
                );
                mv.visitTypeInsn(Opcodes.CHECKCAST, this.getMetadataClass().getInternalName());
                mv.load("this");
                mv.visitFieldInsn(
                        Opcodes.PUTFIELD, 
                        this.getMetadataClass().getInternalName(), 
                        Identifiers.OBJECT_MODEL_FIELD_NAME, 
                        'L' + 
                        this.getMetadataClass().getInternalName() + 
                        '$' + 
                        Identifiers.OBJECT_MODEL_CONTRACT_SIMPLE_NAME + 
                        ';'
                );
            }
            
            if (this.overrideInitAssociations) {
                mv.load("this");
                mv.visitMethodInsn(
                        Opcodes.INVOKESPECIAL, 
                        this.getInternalName(), 
                        Identifiers.INIT_ASSOCIATED_ENDPOINTS_METHOD_NAME, 
                        "()V", 
                        false
                );
            }
            
            for (MetadataProperty metadataProperty : this.getMetadataClass().getDeclaredProperties().values()) {
                if (metadataProperty.getPropertyType() != PropertyType.SCALAR) {
                    continue;
                }
                String stateFieldName = Identifiers.stateFieldName(metadataProperty);
                if (stateFieldName != null) {
                    mv.load("this");
                    mv.load("in");
                    mv.visitMethodInsn(
                            Opcodes.INVOKEVIRTUAL, 
                            ASMConstants.OBJECT_INPUT_STREAM_INTERNAL_NAME, 
                            "readInt", 
                            "()I", 
                            false
                    );
                    mv.visitFieldInsn(
                            Opcodes.PUTFIELD, 
                            this.getInternalName(), 
                            stateFieldName, 
                            "I"
                    );
                }
                String fieldName = Identifiers.fieldName(metadataProperty);
                String fieldDescriptor = this.fieldDescriptor(metadataProperty);
                
                mv.load("this");
                mv.load("in");
                visitIOMethodInvocation(mv, metadataProperty, true);
                if (metadataProperty.getSimpleType() == null || !metadataProperty.getSimpleType().isPrimitive()) {
                    mv.visitTypeInsn(Opcodes.CHECKCAST, this.fieldInternalName(metadataProperty));
                }
                mv.visitFieldInsn(
                        Opcodes.PUTFIELD, 
                        this.getInternalName(), 
                        fieldName, 
                        fieldDescriptor
                );
                if (metadataProperty.getTargetClass() != null) {
                    Label endLabel = new Label();
                    mv.load("this");
                    mv.visitFieldInsn(
                            Opcodes.GETFIELD, 
                            this.getInternalName(), 
                            fieldName, 
                            fieldDescriptor
                    );
                    mv.visitJumpInsn(Opcodes.IFNULL, endLabel);
                    mv.load("this");
                    mv.visitFieldInsn(
                            Opcodes.GETFIELD, 
                            this.getInternalName(), 
                            fieldName, 
                            fieldDescriptor
                    );
                    mv.visitMethodInsn(
                            Opcodes.INVOKEVIRTUAL, 
                            metadataProperty.getTargetClass().getInternalName(), 
                            "objectModel", 
                            "()" + ASMConstants.OBJECT_MODEL_DESCRIPTOR, 
                            false
                    );
                    mv.visitTypeInsn(Opcodes.NEW, ASMConstants.EMBEDDED_SCALAR_LISTENER_IMPL_INTERNAL_NAME);
                    mv.visitInsn(Opcodes.DUP);
                    mv.load("this");
                    mv.visitLdcInsn(metadataProperty.getId());
                    mv.visitMethodInsn(
                            Opcodes.INVOKESPECIAL, 
                            ASMConstants.EMBEDDED_SCALAR_LISTENER_IMPL_INTERNAL_NAME, 
                            "", 
                            '(' + ASMConstants.ABSTRACT_OBJECT_MODEL_IMPL_DESCRITPOR + "I)V", 
                            false
                    );
                    mv.visitMethodInsn(
                            Opcodes.INVOKEINTERFACE, 
                            ASMConstants.OBJECT_MODEL_INTERNAL_NAME, 
                            "addScalarListener", 
                            '(' + ASMConstants.SCALAR_LISTENER_DESCRIPTOR + ")V", 
                            true
                    );
                    mv.visitLabel(endLabel);
                }
            }
            for (MetadataProperty metadataProperty : this.getMetadataClass().getDeclaredProperties().values()) {
                if (metadataProperty.getPropertyType() != PropertyType.ASSOCIATION) {
                    continue;
                }
                boolean assignReadedObject = metadataProperty.getStandardCollectionType() == null;
                if (assignReadedObject) {
                    mv.load("this");
                }
                mv.load("in");
                mv.visitMethodInsn(
                        Opcodes.INVOKEVIRTUAL, 
                        ASMConstants.OBJECT_INPUT_STREAM_INTERNAL_NAME, 
                        "readObject", 
                        "()Ljava/lang/Object;", 
                        false
                );
                if (assignReadedObject) {
                    mv.visitTypeInsn(Opcodes.CHECKCAST, this.fieldInternalName(metadataProperty));
                    mv.visitFieldInsn(
                            Opcodes.PUTFIELD, 
                            this.getInternalName(), 
                            Identifiers.fieldName(metadataProperty), 
                            this.fieldDescriptor(metadataProperty)
                    );
                }
            }
            mv.visitInsn(Opcodes.RETURN);
            
            mv.visitMaxs(0, 0);
            mv.visitEnd();
        }
    }
    
    private static void visitIOMethodInvocation(MethodVisitor mv, MetadataProperty metadataProperty, boolean read) {
        String postfix;
        String desc;
        if (metadataProperty.getSimpleType() != null && metadataProperty.getSimpleType().isPrimitive()) {
            postfix = metadataProperty.getSimpleType().getName();
            postfix = Character.toUpperCase(postfix.charAt(0)) + postfix.substring(1);
            desc = metadataProperty.getDescriptor();
        } else {
            postfix = "Object";
            desc = "Ljava/lang/Object;";
        }
        mv.visitMethodInsn(
                Opcodes.INVOKEVIRTUAL, 
                read ? ASMConstants.OBJECT_INPUT_STREAM_INTERNAL_NAME : ASMConstants.OBJECT_OUTPUT_STREAM_INTERNAL_NAME, 
                (read ? "read" : "write") + postfix, 
                read ? "()" + desc : '(' + desc + ")V", 
                false
        );
    }

    private void generateStaticConstructor(ClassVisitor cv) {
        
        MethodVisitor mv = cv.visitMethod(Opcodes.ACC_STATIC, "", "()V", null, null);
        mv.visitCode();
        
        if (this.overrideLoadScalars) {
            this.generateInitDeferrableScalarPropertyIdsInsn(mv);
        }
        if (!this.getParent().isProxySupported()) {
            this.generateInitComparatorStaticFieldInsns(mv);
        }
        
        mv.visitInsn(Opcodes.RETURN);
        
        mv.visitMaxs(0, 0);
        mv.visitEnd();
    }
    
    private void generateInitDeferrableScalarPropertyIdsInsn(MethodVisitor mv) {
        
        List deferrableScalarPropertyIds = new ArrayList<>();
        for (MetadataProperty metadataProperty : this.getMetadataClass().getPropertyList()) {
            if (metadataProperty.getPropertyType() == PropertyType.SCALAR && metadataProperty.isDeferrable()) {
                deferrableScalarPropertyIds.add(metadataProperty.getId());
            }
        }
        mv.visitLdcInsn(deferrableScalarPropertyIds.size());
        mv.visitIntInsn(Opcodes.NEWARRAY, Opcodes.T_INT);
        int index = 0;
        for (Integer deferrablePropertyId : deferrableScalarPropertyIds) {
            mv.visitInsn(Opcodes.DUP);
            mv.visitLdcInsn(index++);
            mv.visitLdcInsn(deferrablePropertyId);
            mv.visitInsn(Opcodes.IASTORE);
        }
        mv.visitFieldInsn(
                Opcodes.PUTSTATIC,
                this.getInternalName(),
                Identifiers.DEFERRABLE_SCALAR_PROPERTY_IDS, 
                "[I"
        );
    }

    private static MetadataProperty baseProperty(MetadataProperty metadataProperty) {
        switch (metadataProperty.getPropertyType()) {
        case SCALAR:
            return metadataProperty;
        case INDEX:
        case KEY:
            return baseProperty(metadataProperty.getReferenceProperty());
        default:
            while (true) {
                MetadataProperty convarianceProperty = metadataProperty.getConvarianceProperty();
                if (convarianceProperty == null) {
                    break;
                }
                metadataProperty = convarianceProperty;
            }
            return metadataProperty;
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy