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

org.babyfish.model.instrument.spi.AbstractObjectModelGenerator 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.Collection;
import java.util.List;
import java.util.Set;

import org.babyfish.collection.LinkedHashSet;
import org.babyfish.collection.ReferenceEqualityComparator;
import org.babyfish.lang.bytecode.ASMUtils;
import org.babyfish.lang.bytecode.VariableScope;
import org.babyfish.lang.bytecode.VariableScopeBuilder;
import org.babyfish.lang.instrument.bytecode.NestedClassGenerator;
import org.babyfish.model.NullComparatorType;
import org.babyfish.model.StringComparatorType;
import org.babyfish.model.instrument.metadata.MetadataClass;
import org.babyfish.model.instrument.metadata.MetadataProperty;
import org.babyfish.model.metadata.PropertyType;
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;

/**
 * @author Tao Chen
 */
public abstract class AbstractObjectModelGenerator extends NestedClassGenerator {

    protected AbstractObjectModelGenerator(AbstractModelReplacer parent, String simpleName) {
        super(parent, simpleName);
    }
    
    protected final MetadataClass getMetadataClass() {
        return this.getRoot().getMetadataClass();
    }
    
    final void generateHashCodeScalarMethod(ClassVisitor cv, boolean itf) {
        
        List propertyList = this.getMetadataClass().getPropertyList();    
        
        MethodVisitor mv = cv.visitMethod(
                Opcodes.ACC_PUBLIC, 
                "hashCodeScalar", 
                "(I" + ASMConstants.STRING_COMPARATOR_TYPE_DESCRIPTOR + ")I", 
                null,
                null
        );
        mv.visitCode();
        
        try (VariableScope scope = 
                new VariableScopeBuilder()
                .parameter("this", this.getDescriptor())
                .parameter("scalarPropertyId", "I")
                .parameter("stringCompartorType", ASMConstants.STRING_COMPARATOR_TYPE_DESCRIPTOR)
                .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, 
                                "hashCodeNonScalar", 
                                "(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, 
                                "hashCodeArray", 
                                "(Ljava/lang/Class;ILjava/lang/String;)" + ASMConstants.RUNTIME_EXCEPTION_DESCRIPTOR, 
                                false
                        );
                        mv.visitInsn(Opcodes.ATHROW);
                    } else {
                        mv.visitVarInsn(Opcodes.ALOAD, 0);
                        mv.visitMethodInsn(
                                itf ? Opcodes.INVOKEINTERFACE : Opcodes.INVOKEVIRTUAL, 
                                this.getInternalName(), 
                                Identifiers.getterName(metadataProperty), 
                                "()" + metadataProperty.getDescriptor(), 
                                itf
                        );
                        switch (metadataProperty.getDescriptor().charAt(0)) {
                        case 'Z':
                            Label falseLabel = new Label();
                            Label endLabel = new Label();
                            mv.visitJumpInsn(Opcodes.IFEQ, falseLabel);
                            mv.visitLdcInsn(1031);
                            mv.visitJumpInsn(Opcodes.GOTO, endLabel);
                            mv.visitLabel(falseLabel);
                            mv.visitLdcInsn(1037);
                            mv.visitLabel(endLabel);
                            mv.visitInsn(Opcodes.IRETURN);
                            break;
                        case 'C':
                        case 'B':
                        case 'S':
                        case 'I':
                            mv.visitInsn(Opcodes.IRETURN);
                            break;
                        case 'F':
                            mv.visitMethodInsn(
                                    Opcodes.INVOKESTATIC, 
                                    "java/lang/Float", 
                                    "floatToIntBits", 
                                    "(F)I", 
                                    false
                            );
                            mv.visitInsn(Opcodes.IRETURN);
                            break;
                        case 'D':
                            mv.visitMethodInsn(
                                    Opcodes.INVOKESTATIC, 
                                    "java/lang/Double", 
                                    "doubleToLongBits", 
                                    "(D)J", 
                                    false
                            );
                            // No break
                        case 'J':
                            try (VariableScope childScope = scope.createSubScope()) {
                                childScope.declare("bits", "J", null);
                                childScope.store("bits");
                                childScope.load("bits");
                                mv.visitIntInsn(Opcodes.BIPUSH, 32);
                                mv.visitInsn(Opcodes.LUSHR);
                                childScope.load("bits");
                                mv.visitInsn(Opcodes.LXOR);
                                mv.visitInsn(Opcodes.L2I);
                                mv.visitInsn(Opcodes.IRETURN);
                            }
                            break;
                        default:
                            try (VariableScope childScope = scope.createSubScope()) {
                                childScope.declare("value", metadataProperty.getDescriptor(), null);
                                childScope.store("value");
                                
                                Label notNullLabel = new Label();
                                Label endObjectLabel = new Label();
                                childScope.load("value");
                                mv.visitJumpInsn(Opcodes.IFNONNULL, notNullLabel);
                                mv.visitInsn(Opcodes.ICONST_0);
                                mv.visitJumpInsn(Opcodes.GOTO, endObjectLabel);
                                mv.visitLabel(notNullLabel);
                                if (metadataProperty.getSimpleType() == String.class) {
                                    Label endStringComparatorTypeCheckLabel = new Label();
                                    mv.visitVarInsn(Opcodes.ALOAD, 2);
                                    ASMUtils.visitEnumLdc(mv, StringComparatorType.INSENSITIVE);
                                    mv.visitJumpInsn(Opcodes.IF_ACMPNE, endStringComparatorTypeCheckLabel);
                                    childScope.load("value");
                                    mv.visitJumpInsn(Opcodes.IFNULL, endStringComparatorTypeCheckLabel);
                                    childScope.load("value");
                                    mv.visitMethodInsn(
                                            Opcodes.INVOKEVIRTUAL, 
                                            "java/lang/String", 
                                            "toUpperCase", 
                                            "()Ljava/lang/String;", 
                                            false
                                    );
                                    childScope.store("value");
                                    mv.visitLabel(endStringComparatorTypeCheckLabel);
                                }
                                if (metadataProperty.getTargetClass() != null) {
                                    mv.visitFieldInsn(
                                            Opcodes.GETSTATIC, 
                                            this.getInternalName(), 
                                            Identifiers.embeddedEqualityComparatorStaticFieldName(metadataProperty), 
                                            ASMConstants.EQUALITY_COMPARATOR_DESCRIPTOR
                                    );
                                    childScope.load("value");
                                    mv.visitMethodInsn(
                                            Opcodes.INVOKEINTERFACE, 
                                            ASMConstants.EQUALITY_COMPARATOR_INTERNAL_NAME, 
                                            "hashCode", 
                                            "(Ljava/lang/Object;)I", 
                                            true
                                    );
                                } else {
                                    childScope.load("value");
                                    mv.visitMethodInsn(
                                            Opcodes.INVOKEVIRTUAL, 
                                            "java/lang/Object", 
                                            "hashCode", 
                                            "()I", 
                                            false
                                    );
                                }
                                mv.visitLabel(endObjectLabel);
                                mv.visitInsn(Opcodes.IRETURN);
                            }
                            break;
                        }
                    }
                }
                
                mv.visitLabel(defaultLabel);
            }
            
            ASMUtils.visitClassLdc(mv, this.getMetadataClass().getDescriptor());
            scope.load("scalarPropertyId");
            mv.visitMethodInsn(
                    Opcodes.INVOKESTATIC, 
                    ASMConstants.OBJECT_MODEL_EXCEPTIONS_INTERNAL_NAME, 
                    "hashCodeNonExisting", 
                    "(Ljava/lang/Class;I)" + ASMConstants.RUNTIME_EXCEPTION_DESCRIPTOR, 
                    false
            );
            mv.visitInsn(Opcodes.ATHROW);
        }
        
        mv.visitMaxs(0, 0);
        mv.visitEnd();
    }
    
    final void generateEqualsScalarMethod(ClassVisitor cv, boolean itf) {
        
        List propertyList = this.getMetadataClass().getPropertyList();    
        
        MethodVisitor mv = cv.visitMethod(
                Opcodes.ACC_PUBLIC, 
                "equalsScalar", 
                "(I" + 
                ASMConstants.STRING_COMPARATOR_TYPE_DESCRIPTOR + 
                ASMConstants.OBJECT_MODEL_DESCRIPTOR +  
                ")Z", 
                null,
                null
        );
        mv.visitCode();
        
        try (VariableScope scope = 
                new VariableScopeBuilder()
                .parameter("this", this.getDescriptor())
                .parameter("scalarPropertyId", "I")
                .parameter("stringComparatorType", ASMConstants.STRING_COMPARATOR_TYPE_DESCRIPTOR)
                .parameter("other", ASMConstants.OBJECT_MODEL_DESCRIPTOR)
                .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, 
                                "equalsNonScalar", 
                                "(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, 
                                "equalsArray", 
                                "(Ljava/lang/Class;ILjava/lang/String;)" + ASMConstants.RUNTIME_EXCEPTION_DESCRIPTOR, 
                                false
                        );
                        mv.visitInsn(Opcodes.ATHROW);
                    } else if (metadataProperty.getDescriptor().charAt(0) == 'L') {
                        try (VariableScope childScope = scope.createSubScope()) {
                            childScope.declare("left", metadataProperty.getDescriptor(), null);
                            childScope.declare("right", metadataProperty.getDescriptor(), null);
                            Label leftIsNotNullLabel = new Label();
                            Label rightIsNotNullWhenLeftIsNullLabel = new Label();
                            Label endNestedIfLabel = new Label();
                            
                            scope.load("this");
                            mv.visitMethodInsn(
                                    itf ? Opcodes.INVOKEINTERFACE : Opcodes.INVOKEVIRTUAL, 
                                    this.getInternalName(), 
                                    Identifiers.getterName(metadataProperty), 
                                    "()" + metadataProperty.getDescriptor(), 
                                    itf
                            );
                            childScope.store("left");
                            
                            scope.load("other");
                            mv.visitTypeInsn(Opcodes.CHECKCAST, this.getInternalName());
                            mv.visitMethodInsn(
                                    itf ? Opcodes.INVOKEINTERFACE : Opcodes.INVOKEVIRTUAL, 
                                    this.getInternalName(), 
                                    Identifiers.getterName(metadataProperty), 
                                    "()" + metadataProperty.getDescriptor(), 
                                    itf
                            );
                            childScope.store("right");
                            
                            childScope.load("left");
                            mv.visitJumpInsn(Opcodes.IFNONNULL, leftIsNotNullLabel);
                            childScope.load("right");
                            mv.visitJumpInsn(Opcodes.IFNONNULL, rightIsNotNullWhenLeftIsNullLabel);
                            mv.visitInsn(Opcodes.ICONST_1);
                            mv.visitJumpInsn(Opcodes.GOTO, endNestedIfLabel);
                            mv.visitLabel(rightIsNotNullWhenLeftIsNullLabel);
                            mv.visitInsn(Opcodes.ICONST_0);
                            mv.visitLabel(endNestedIfLabel);
                            mv.visitInsn(Opcodes.IRETURN);
                            
                            mv.visitLabel(leftIsNotNullLabel);
                            if (metadataProperty.getSimpleType() == String.class) {
                                Label finishedStringComparatorTypeCheckLabel = new Label();
                                Label rightIsNotNullWhenLeftIsNotNullLabel = new Label();
                                scope.load("stringComparatorType");
                                ASMUtils.visitEnumLdc(mv, StringComparatorType.INSENSITIVE);
                                mv.visitJumpInsn(Opcodes.IF_ACMPNE, finishedStringComparatorTypeCheckLabel);
                                childScope.load("right");
                                mv.visitJumpInsn(Opcodes.IFNONNULL, rightIsNotNullWhenLeftIsNotNullLabel);
                                mv.visitInsn(Opcodes.ICONST_0);
                                mv.visitInsn(Opcodes.IRETURN);
                                mv.visitLabel(rightIsNotNullWhenLeftIsNotNullLabel);
                                childScope.load("left");
                                mv.visitMethodInsn(
                                        Opcodes.INVOKEVIRTUAL, 
                                        "java/lang/String", 
                                        "toUpperCase", 
                                        "()Ljava/lang/String;", 
                                        false
                                );
                                childScope.store("left");
                                childScope.load("right");
                                mv.visitMethodInsn(
                                        Opcodes.INVOKEVIRTUAL, 
                                        "java/lang/String", 
                                        "toUpperCase", 
                                        "()Ljava/lang/String;", 
                                        false
                                );
                                childScope.store("right");
                                mv.visitLabel(finishedStringComparatorTypeCheckLabel);
                            }
                            
                            if (metadataProperty.getTargetClass() != null) {
                                mv.visitFieldInsn(
                                        Opcodes.GETSTATIC, 
                                        this.getInternalName(), 
                                        Identifiers.embeddedEqualityComparatorStaticFieldName(metadataProperty), 
                                        ASMConstants.EQUALITY_COMPARATOR_DESCRIPTOR
                                );
                                childScope.load("left");
                                childScope.load("right");
                                mv.visitMethodInsn(
                                        Opcodes.INVOKEINTERFACE, 
                                        ASMConstants.EQUALITY_COMPARATOR_INTERNAL_NAME, 
                                        "equals", 
                                        "(Ljava/lang/Object;Ljava/lang/Object;)Z", 
                                        true
                                );
                            } else {
                                childScope.load("left");
                                childScope.load("right");
                                mv.visitMethodInsn(
                                        Opcodes.INVOKEVIRTUAL, 
                                        "java/lang/Object", 
                                        "equals", 
                                        "(Ljava/lang/Object;)Z", 
                                        false
                                );
                            }
                            mv.visitInsn(Opcodes.IRETURN);
                        }
                    } else {
                        scope.load("this");
                        mv.visitMethodInsn(
                                itf ? Opcodes.INVOKEINTERFACE : Opcodes.INVOKEVIRTUAL, 
                                this.getInternalName(), 
                                Identifiers.getterName(metadataProperty), 
                                "()" + metadataProperty.getDescriptor(), 
                                itf
                        );
                        scope.load("other");
                        mv.visitTypeInsn(Opcodes.CHECKCAST, this.getInternalName());
                        mv.visitMethodInsn(
                                itf ? Opcodes.INVOKEINTERFACE : Opcodes.INVOKEVIRTUAL, 
                                this.getInternalName(), 
                                Identifiers.getterName(metadataProperty), 
                                "()" + metadataProperty.getDescriptor(), 
                                itf
                        );
                        Label falseLabel = new Label();
                        Label endLabel = new Label();
                        switch (metadataProperty.getDescriptor().charAt(0)) {
                        case 'Z':
                        case 'C':
                        case 'B':
                        case 'S':
                        case 'I':
                            mv.visitJumpInsn(Opcodes.IF_ICMPNE, falseLabel);
                            mv.visitInsn(Opcodes.ICONST_1);
                            mv.visitJumpInsn(Opcodes.GOTO, endLabel);
                            mv.visitLabel(falseLabel);
                            mv.visitInsn(Opcodes.ICONST_0);
                            mv.visitLabel(endLabel);
                            mv.visitInsn(Opcodes.IRETURN);
                            break;
                        case 'F':
                            mv.visitInsn(Opcodes.FCMPL);
                            mv.visitJumpInsn(Opcodes.IFNE, falseLabel);
                            mv.visitInsn(Opcodes.ICONST_1);
                            mv.visitJumpInsn(Opcodes.GOTO, endLabel);
                            mv.visitLabel(falseLabel);
                            mv.visitInsn(Opcodes.ICONST_0);
                            mv.visitLabel(endLabel);
                            mv.visitInsn(Opcodes.IRETURN);
                            break;
                        case 'D':
                            mv.visitInsn(Opcodes.DCMPL);
                            mv.visitJumpInsn(Opcodes.IFNE, falseLabel);
                            mv.visitInsn(Opcodes.ICONST_1);
                            mv.visitJumpInsn(Opcodes.GOTO, endLabel);
                            mv.visitLabel(falseLabel);
                            mv.visitInsn(Opcodes.ICONST_0);
                            mv.visitLabel(endLabel);
                            mv.visitInsn(Opcodes.IRETURN);
                            break;
                        case 'J':
                            mv.visitInsn(Opcodes.LCMP);
                            mv.visitJumpInsn(Opcodes.IFNE, falseLabel);
                            mv.visitInsn(Opcodes.ICONST_1);
                            mv.visitJumpInsn(Opcodes.GOTO, endLabel);
                            mv.visitLabel(falseLabel);
                            mv.visitInsn(Opcodes.ICONST_0);
                            mv.visitLabel(endLabel);
                            mv.visitInsn(Opcodes.IRETURN);
                            break;
                        default:
                            throw new AssertionError("Internal bug");
                        }
                    }
                }
                
                mv.visitLabel(defaultLabel);
            }
            
            ASMUtils.visitClassLdc(mv, this.getMetadataClass().getDescriptor());
            scope.load("scalarPropertyId");
            mv.visitMethodInsn(
                    Opcodes.INVOKESTATIC, 
                    ASMConstants.OBJECT_MODEL_EXCEPTIONS_INTERNAL_NAME, 
                    "equalsNonExisting", 
                    "(Ljava/lang/Class;I)" + ASMConstants.RUNTIME_EXCEPTION_DESCRIPTOR, 
                    false
            );
            mv.visitInsn(Opcodes.ATHROW);
        }
        
        mv.visitMaxs(0, 0);
        mv.visitEnd();
    }
    
    final void generateCompareScalarMethod(ClassVisitor cv, boolean itf) {
        
        List propertyList = this.getMetadataClass().getPropertyList();    
        
        MethodVisitor mv = cv.visitMethod(
                Opcodes.ACC_PUBLIC, 
                "compareScalar", 
                "(I" +  
                ASMConstants.STRING_COMPARATOR_TYPE_DESCRIPTOR + 
                ASMConstants.NULL_COMPARATOR_TYPE_DESCRIPTOR + 
                ASMConstants.OBJECT_MODEL_DESCRIPTOR + 
                ")I", 
                null,
                null
        );
        mv.visitCode();
        
        try (VariableScope scope = 
                new VariableScopeBuilder()
                .parameter("this", this.getDescriptor())
                .parameter("scalarPropertyId", "I")
                .parameter("stringComparatorType", ASMConstants.STRING_COMPARATOR_TYPE_DESCRIPTOR)
                .parameter("nullComparatorType", ASMConstants.NULL_COMPARATOR_TYPE_DESCRIPTOR)
                .parameter("other", ASMConstants.OBJECT_MODEL_DESCRIPTOR)
                .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, 
                                "compareNonScalar", 
                                "(Ljava/lang/Class;ILjava/lang/String;)" + ASMConstants.RUNTIME_EXCEPTION_DESCRIPTOR, 
                                false
                        );
                        mv.visitInsn(Opcodes.ATHROW);
                    } else {
                        char descChar = metadataProperty.getDescriptor().charAt(0);
                        if (descChar == '[') {
                            ASMUtils.visitClassLdc(mv, metadataProperty.getDeclaringClass().getDescriptor());
                            mv.visitLdcInsn(metadataProperty.getId());
                            mv.visitLdcInsn(metadataProperty.getName());
                            mv.visitMethodInsn(
                                    Opcodes.INVOKESTATIC, 
                                    ASMConstants.OBJECT_MODEL_EXCEPTIONS_INTERNAL_NAME, 
                                    "compareArray", 
                                    "(Ljava/lang/Class;ILjava/lang/String;)" + ASMConstants.RUNTIME_EXCEPTION_DESCRIPTOR, 
                                    false
                            );
                            mv.visitInsn(Opcodes.ATHROW);
                        } else if (descChar == 'L') {
                            try (VariableScope childScope = scope.createSubScope()) {
                                childScope.declare("left", metadataProperty.getDescriptor(), null);
                                childScope.declare("right", metadataProperty.getDescriptor(), null);
                                Label notSameLabel = new Label();
                                Label leftIsNotNullLabel = new Label();
                                Label rightIsNotNullLabel = new Label();
                                Label nullsLastWhenLeftIsNullLabel = new Label();
                                Label nullsLastWhenRightIsNullLabel = new Label();
                                Label returnWhenLeftIsNullLabel = new Label();
                                Label returnWhenRightIsNullLabel = new Label();
                                
                                scope.load("this");
                                mv.visitMethodInsn(
                                        itf ? Opcodes.INVOKEINTERFACE : Opcodes.INVOKEVIRTUAL, 
                                        this.getInternalName(), 
                                        Identifiers.getterName(metadataProperty), 
                                        "()" + metadataProperty.getDescriptor(), 
                                        itf
                                );
                                childScope.store("left");
                                
                                scope.load("other");
                                mv.visitTypeInsn(Opcodes.CHECKCAST, this.getInternalName());
                                mv.visitMethodInsn(
                                        itf ? Opcodes.INVOKEINTERFACE : Opcodes.INVOKEVIRTUAL, 
                                        this.getInternalName(), 
                                        Identifiers.getterName(metadataProperty), 
                                        "()" + metadataProperty.getDescriptor(), 
                                        itf
                                );
                                childScope.store("right");
                                
                                childScope.load("left");
                                childScope.load("right");
                                mv.visitJumpInsn(Opcodes.IF_ACMPNE, notSameLabel);
                                mv.visitInsn(Opcodes.ICONST_0);
                                mv.visitInsn(Opcodes.IRETURN);
                                mv.visitLabel(notSameLabel);
                                
                                childScope.load("left");
                                mv.visitJumpInsn(Opcodes.IFNONNULL, leftIsNotNullLabel);
                                scope.load("nullComparatorType");
                                ASMUtils.visitEnumLdc(mv, NullComparatorType.NULLS_LAST);
                                mv.visitJumpInsn(Opcodes.IF_ACMPEQ, nullsLastWhenLeftIsNullLabel);
                                mv.visitInsn(Opcodes.ICONST_M1);
                                mv.visitJumpInsn(Opcodes.GOTO, returnWhenLeftIsNullLabel);
                                mv.visitLabel(nullsLastWhenLeftIsNullLabel);
                                mv.visitInsn(Opcodes.ICONST_1);
                                mv.visitLabel(returnWhenLeftIsNullLabel);
                                mv.visitInsn(Opcodes.IRETURN);
                                mv.visitLabel(leftIsNotNullLabel);
                                
                                childScope.load("right");
                                mv.visitJumpInsn(Opcodes.IFNONNULL, rightIsNotNullLabel);
                                scope.load("nullComparatorType");
                                ASMUtils.visitEnumLdc(mv, NullComparatorType.NULLS_LAST);
                                mv.visitJumpInsn(Opcodes.IF_ACMPEQ, nullsLastWhenRightIsNullLabel);
                                mv.visitInsn(Opcodes.ICONST_1);
                                mv.visitJumpInsn(Opcodes.GOTO, returnWhenRightIsNullLabel);
                                mv.visitLabel(nullsLastWhenRightIsNullLabel);
                                mv.visitInsn(Opcodes.ICONST_M1);
                                mv.visitLabel(returnWhenRightIsNullLabel);
                                mv.visitInsn(Opcodes.IRETURN);
                                mv.visitLabel(rightIsNotNullLabel);
                                
                                if (metadataProperty.getSimpleType() == String.class) {
                                    Label finishInsensitiveLabel = new Label();
                                    scope.load("stringComparatorType");
                                    ASMUtils.visitEnumLdc(mv, StringComparatorType.INSENSITIVE);
                                    mv.visitJumpInsn(Opcodes.IF_ACMPNE, finishInsensitiveLabel);
                                    childScope.load("left");
                                    mv.visitMethodInsn(
                                            Opcodes.INVOKEVIRTUAL, 
                                            "java/lang/String", 
                                            "toUpperCase", 
                                            "()Ljava/lang/String;",
                                            false
                                    );
                                    childScope.store("left");
                                    childScope.load("right");
                                    mv.visitMethodInsn(
                                            Opcodes.INVOKEVIRTUAL, 
                                            "java/lang/String", 
                                            "toUpperCase", 
                                            "()Ljava/lang/String;",
                                            false
                                    );
                                    childScope.store("right");
                                    mv.visitLabel(finishInsensitiveLabel);
                                }
                                
                                if (metadataProperty.getTargetClass() != null) {
                                    mv.visitFieldInsn(
                                            Opcodes.GETSTATIC, 
                                            this.getInternalName(), 
                                            Identifiers.embeddedComparatorStaticFieldName(metadataProperty), 
                                            ASMConstants.COMPARATOR_DESCRIPTOR
                                    );
                                    childScope.load("left");
                                    childScope.load("right");
                                    mv.visitMethodInsn(
                                            Opcodes.INVOKEINTERFACE, 
                                            ASMConstants.COMPARATOR_INTERNAL_NAME, 
                                            "compare", 
                                            "(Ljava/lang/Object;Ljava/lang/Object;)I",
                                            true
                                    );
                                } else {
                                    childScope.load("left");
                                    if (metadataProperty.getSimpleType() == null) {
                                        mv.visitTypeInsn(Opcodes.CHECKCAST, "java/lang/Comparable");
                                    }
                                    childScope.load("right");
                                    mv.visitMethodInsn(
                                            Opcodes.INVOKEINTERFACE, 
                                            "java/lang/Comparable", 
                                            "compareTo", 
                                            "(Ljava/lang/Object;)I", 
                                            true
                                    );
                                }
                                mv.visitInsn(Opcodes.IRETURN);
                            }
                        } else {
                            try (VariableScope childScope = scope.createSubScope()) {
                                
                                Label notEqLabel = new Label();
                                Label geLabel = new Label();
                                Label endLabel = new Label();
                                childScope.declare("left", metadataProperty.getDescriptor(), null);
                                childScope.declare("right", metadataProperty.getDescriptor(), null);
                                scope.load("this");
                                mv.visitMethodInsn(
                                        itf ? Opcodes.INVOKEINTERFACE : Opcodes.INVOKEVIRTUAL, 
                                        this.getInternalName(), 
                                        Identifiers.getterName(metadataProperty), 
                                        "()" + metadataProperty.getDescriptor(), 
                                        itf
                                );
                                childScope.store("left");
                                scope.load("other");
                                mv.visitTypeInsn(Opcodes.CHECKCAST, this.getInternalName());
                                mv.visitMethodInsn(
                                        itf ? Opcodes.INVOKEINTERFACE : Opcodes.INVOKEVIRTUAL, 
                                        this.getInternalName(), 
                                        Identifiers.getterName(metadataProperty), 
                                        "()" + metadataProperty.getDescriptor(), 
                                        itf
                                );
                                childScope.store("right");
                                
                                switch (descChar) {
                                case 'Z':
                                    childScope.load("left");
                                    childScope.load("right");
                                    mv.visitJumpInsn(Opcodes.IF_ICMPNE, notEqLabel);
                                    mv.visitInsn(Opcodes.ICONST_0);
                                    mv.visitInsn(Opcodes.IRETURN);
                                    mv.visitLabel(notEqLabel);
                                    childScope.load("left");
                                    mv.visitJumpInsn(Opcodes.IFNE, geLabel);
                                    mv.visitInsn(Opcodes.ICONST_M1);
                                    mv.visitJumpInsn(Opcodes.GOTO, endLabel);
                                    mv.visitLabel(geLabel);
                                    mv.visitInsn(Opcodes.ICONST_1);
                                    mv.visitLabel(endLabel);
                                    mv.visitInsn(Opcodes.IRETURN);
                                    mv.visitEnd();
                                    break;
                                case 'C':
                                case 'B':
                                case 'S':
                                case 'I':
                                    childScope.load("left");
                                    childScope.load("right");
                                    mv.visitJumpInsn(Opcodes.IF_ICMPNE, notEqLabel);
                                    mv.visitInsn(Opcodes.ICONST_0);
                                    mv.visitInsn(Opcodes.IRETURN);
                                    mv.visitLabel(notEqLabel);
                                    childScope.load("left");
                                    childScope.load("right");
                                    mv.visitJumpInsn(Opcodes.IF_ICMPGE, geLabel);
                                    mv.visitInsn(Opcodes.ICONST_M1);
                                    mv.visitJumpInsn(Opcodes.GOTO, endLabel);
                                    mv.visitLabel(geLabel);
                                    mv.visitInsn(Opcodes.ICONST_1);
                                    mv.visitLabel(endLabel);
                                    mv.visitInsn(Opcodes.IRETURN);
                                    break;
                                case 'J':
                                    childScope.load("left");
                                    childScope.load("right");
                                    mv.visitInsn(Opcodes.LCMP);
                                    mv.visitJumpInsn(Opcodes.IFNE, notEqLabel);
                                    mv.visitInsn(Opcodes.ICONST_0);
                                    mv.visitInsn(Opcodes.IRETURN);
                                    mv.visitLabel(notEqLabel);
                                    childScope.load("left");
                                    childScope.load("right");
                                    mv.visitInsn(Opcodes.LCMP);
                                    mv.visitJumpInsn(Opcodes.IFGE, geLabel);
                                    mv.visitInsn(Opcodes.ICONST_M1);
                                    mv.visitJumpInsn(Opcodes.GOTO, endLabel);
                                    mv.visitLabel(geLabel);
                                    mv.visitInsn(Opcodes.ICONST_1);
                                    mv.visitLabel(endLabel);
                                    mv.visitInsn(Opcodes.IRETURN);
                                    break;
                                case 'F':
                                    childScope.load("left");
                                    childScope.load("right");
                                    mv.visitInsn(Opcodes.FCMPL);
                                    mv.visitJumpInsn(Opcodes.IFNE, notEqLabel);
                                    mv.visitInsn(Opcodes.ICONST_0);
                                    mv.visitInsn(Opcodes.IRETURN);
                                    mv.visitLabel(notEqLabel);
                                    childScope.load("left");
                                    childScope.load("right");
                                    mv.visitInsn(Opcodes.FCMPG);
                                    mv.visitJumpInsn(Opcodes.IFGE, geLabel);
                                    mv.visitInsn(Opcodes.ICONST_M1);
                                    mv.visitJumpInsn(Opcodes.GOTO, endLabel);
                                    mv.visitLabel(geLabel);
                                    mv.visitInsn(Opcodes.ICONST_1);
                                    mv.visitLabel(endLabel);
                                    mv.visitInsn(Opcodes.IRETURN);
                                    break;
                                case 'D':
                                    childScope.load("left");
                                    childScope.load("right");
                                    mv.visitInsn(Opcodes.DCMPL);
                                    mv.visitJumpInsn(Opcodes.IFNE, notEqLabel);
                                    mv.visitInsn(Opcodes.ICONST_0);
                                    mv.visitInsn(Opcodes.IRETURN);
                                    mv.visitLabel(notEqLabel);
                                    childScope.load("left");
                                    childScope.load("right");
                                    mv.visitInsn(Opcodes.DCMPG);
                                    mv.visitJumpInsn(Opcodes.IFGE, geLabel);
                                    mv.visitInsn(Opcodes.ICONST_M1);
                                    mv.visitJumpInsn(Opcodes.GOTO, endLabel);
                                    mv.visitLabel(geLabel);
                                    mv.visitInsn(Opcodes.ICONST_1);
                                    mv.visitLabel(endLabel);
                                    mv.visitInsn(Opcodes.IRETURN);
                                    break;
                                default:
                                    throw new AssertionError("Internal bug");
                                }
                            }
                        }
                    }
                }
                
                mv.visitLabel(defaultLabel);
            }
            
            ASMUtils.visitClassLdc(mv, this.getMetadataClass().getDescriptor());
            scope.load("scalarPropertyId");
            mv.visitMethodInsn(
                    Opcodes.INVOKESTATIC, 
                    ASMConstants.OBJECT_MODEL_EXCEPTIONS_INTERNAL_NAME, 
                    "equalsNonExisting", 
                    "(Ljava/lang/Class;I)" + ASMConstants.RUNTIME_EXCEPTION_DESCRIPTOR, 
                    false
            );
            mv.visitInsn(Opcodes.ATHROW);
        }
        
        mv.visitMaxs(0, 0);
        mv.visitEnd();
    }
    
    final void generateEmbededComparatorStaticFields(ClassVisitor cv) {
        for (MetadataClass embeddedMetadataClass : this.getEmbeddedMetadataClasses()) {
            cv
            .visitField(
                    Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL, 
                    Identifiers.embeddedComparatorStaticFieldName(embeddedMetadataClass), 
                    ASMConstants.COMPARATOR_DESCRIPTOR, 
                    null,
                    null
            )
            .visitEnd();
            cv
            .visitField(
                    Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL, 
                    Identifiers.embeddedEqualityComparatorStaticFieldName(embeddedMetadataClass), 
                    ASMConstants.EQUALITY_COMPARATOR_DESCRIPTOR, 
                    null,
                    null
            )
            .visitEnd();
        }
    }
    
    final void generateInitComparatorStaticFieldInsns(MethodVisitor mv) {
        for (MetadataClass embeddedMetadataClass : this.getEmbeddedMetadataClasses()) {
            ASMUtils.visitClassLdc(mv, embeddedMetadataClass.getDescriptor());
            mv.visitMethodInsn(
                    Opcodes.INVOKESTATIC, 
                    ASMConstants.MODEL_CLASS_INTERNAL_NAME, 
                    "of", 
                    "(Ljava/lang/Class;)" + ASMConstants.MODEL_CLASS_DESCRIPTOR, 
                    false
            );
            mv.visitMethodInsn(
                    Opcodes.INVOKEINTERFACE, 
                    ASMConstants.MODEL_CLASS_INTERNAL_NAME, 
                    "getDefaultComparator", 
                    "()" + ASMConstants.COMPARATOR_DESCRIPTOR,
                    true
            );
            mv.visitFieldInsn(
                    Opcodes.PUTSTATIC, 
                    this.getInternalName(), 
                    Identifiers.embeddedComparatorStaticFieldName(embeddedMetadataClass), 
                    ASMConstants.COMPARATOR_DESCRIPTOR
            );
            
            ASMUtils.visitClassLdc(mv, embeddedMetadataClass.getDescriptor());
            mv.visitMethodInsn(
                    Opcodes.INVOKESTATIC, 
                    ASMConstants.MODEL_CLASS_INTERNAL_NAME, 
                    "of", 
                    "(Ljava/lang/Class;)" + ASMConstants.MODEL_CLASS_DESCRIPTOR, 
                    false
            );
            mv.visitMethodInsn(
                    Opcodes.INVOKEINTERFACE, 
                    ASMConstants.MODEL_CLASS_INTERNAL_NAME, 
                    "getDefaultEqualityComparator", 
                    "()" + ASMConstants.EQUALITY_COMPARATOR_DESCRIPTOR,
                    true
            );
            mv.visitFieldInsn(
                    Opcodes.PUTSTATIC, 
                    this.getInternalName(), 
                    Identifiers.embeddedEqualityComparatorStaticFieldName(embeddedMetadataClass), 
                    ASMConstants.EQUALITY_COMPARATOR_DESCRIPTOR
            );
        }
    }
    
    private Collection getEmbeddedMetadataClasses() {
        Set embededdMetadataClasses = new LinkedHashSet<>(ReferenceEqualityComparator.getInstance());
        for (MetadataProperty declaredProperty : this.getMetadataClass().getDeclaredProperties().values()) {
            if (declaredProperty.getPropertyType() == PropertyType.SCALAR && declaredProperty.getTargetClass() != null) {
                boolean override = false;
                if (this.getMetadataClass().getSuperClass() != null) {
                    for (MetadataProperty superProperty : this.getMetadataClass().getSuperClass().getProperties().values()) {
                        if (superProperty.getTargetClass() == declaredProperty.getTargetClass()) {
                            override = true;
                            break;
                        }
                    }
                }
                if (!override) {
                    embededdMetadataClasses.add(declaredProperty.getTargetClass());
                }
            }
        }
        return embededdMetadataClasses;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy