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

net.bytebuddy.implementation.EqualsMethod Maven / Gradle / Ivy

Go to download

Byte Buddy is a Java library for creating Java classes at run time. This artifact is a build of Byte Buddy with all ASM dependencies repackaged into its own name space.

There is a newer version: 1.15.11
Show newest version
package net.bytebuddy.implementation;

import net.bytebuddy.ClassFileVersion;
import net.bytebuddy.build.HashCodeAndEqualsPlugin;
import net.bytebuddy.description.field.FieldDescription;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.type.TypeDefinition;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.dynamic.scaffold.InstrumentedType;
import net.bytebuddy.implementation.bytecode.ByteCodeAppender;
import net.bytebuddy.implementation.bytecode.StackManipulation;
import net.bytebuddy.implementation.bytecode.StackSize;
import net.bytebuddy.implementation.bytecode.assign.InstanceCheck;
import net.bytebuddy.implementation.bytecode.assign.TypeCasting;
import net.bytebuddy.implementation.bytecode.constant.IntegerConstant;
import net.bytebuddy.implementation.bytecode.member.FieldAccess;
import net.bytebuddy.implementation.bytecode.member.MethodInvocation;
import net.bytebuddy.implementation.bytecode.member.MethodReturn;
import net.bytebuddy.implementation.bytecode.member.MethodVariableAccess;
import net.bytebuddy.matcher.ElementMatcher;
import net.bytebuddy.jar.asm.Label;
import net.bytebuddy.jar.asm.MethodVisitor;
import net.bytebuddy.jar.asm.Opcodes;
import net.bytebuddy.jar.asm.Type;

import java.util.ArrayList;
import java.util.List;

import static net.bytebuddy.matcher.ElementMatchers.*;

/**
 * An implementation of {@link Object#equals(Object)} that takes a class's declared fields into consideration. Equality is resolved by comparing two
 * instances of the same or a compatible class field by field where reference fields must either both be {@code null} or where the field value of
 * the instance upon which the method is invoked returns {@code true} upon calling the value's {@code equals} method. For arrays, the corresponding
 * utilities of {@link java.util.Arrays} are used.
 */
@HashCodeAndEqualsPlugin.Enhance
public class EqualsMethod implements Implementation {

    /**
     * The {@link Object#equals(Object)} method.
     */
    private static final MethodDescription.InDefinedShape EQUALS = TypeDescription.OBJECT
            .getDeclaredMethods()
            .filter(isEquals())
            .getOnly();

    /**
     * The baseline equality to check.
     */
    private final SuperClassCheck superClassCheck;

    /**
     * The instance type compatibility check.
     */
    private final TypeCompatibilityCheck typeCompatibilityCheck;

    /**
     * A matcher to filter fields that should not be used for a equality resolution.
     */
    private final ElementMatcher.Junction ignored;

    /**
     * A matcher to determine fields of a reference type that cannot be {@code null}.
     */
    private final ElementMatcher.Junction nonNullable;

    /**
     * Creates a new equals method implementation.
     *
     * @param superClassCheck The baseline equality to check.
     */
    protected EqualsMethod(SuperClassCheck superClassCheck) {
        this(superClassCheck, TypeCompatibilityCheck.EXACT, none(), none());
    }

    /**
     * Creates a new equals method implementation.
     *
     * @param superClassCheck        The baseline equality to check.
     * @param typeCompatibilityCheck The instance type compatibility check.
     * @param ignored                A matcher to filter fields that should not be used for a equality resolution.
     * @param nonNullable            A matcher to determine fields of a reference type that cannot be {@code null}.
     */
    private EqualsMethod(SuperClassCheck superClassCheck,
                         TypeCompatibilityCheck typeCompatibilityCheck,
                         ElementMatcher.Junction ignored,
                         ElementMatcher.Junction nonNullable) {
        this.superClassCheck = superClassCheck;
        this.typeCompatibilityCheck = typeCompatibilityCheck;
        this.ignored = ignored;
        this.nonNullable = nonNullable;
    }

    /**
     * Creates an equals implementation that invokes the super class's {@link Object#equals(Object)} method first.
     *
     * @return An equals implementation that invokes the super class's {@link Object#equals(Object)} method first.
     */
    public static EqualsMethod requiringSuperClassEquality() {
        return new EqualsMethod(SuperClassCheck.ENABLED);
    }

    /**
     * Creates an equals method implementation that does not invoke the super class's {@link Object#equals(Object)} method.
     *
     * @return An equals method implementation that does not invoke the super class's {@link Object#equals(Object)} method.
     */
    public static EqualsMethod isolated() {
        return new EqualsMethod(SuperClassCheck.DISABLED);
    }

    /**
     * Returns a new version of this equals method implementation that ignores the specified fields additionally to any
     * previously specified fields.
     *
     * @param ignored A matcher to specify any fields that should be ignored.
     * @return A new version of this equals method implementation that also ignores any fields matched by the provided matcher.
     */
    public EqualsMethod withIgnoredFields(ElementMatcher ignored) {
        return new EqualsMethod(superClassCheck, typeCompatibilityCheck, this.ignored.or(ignored), nonNullable);
    }

    /**
     * Returns a new version of this equals method implementation that does not apply a {@code null} value check for the specified fields
     * if they have a reference type additionally to any previously specified fields.
     *
     * @param nonNullable A matcher to specify any fields that should not be guarded against {@code null} values.
     * @return A new version of this equals method implementation that also does not apply {@code null} value checks to any fields matched by
     * the provided matcher.
     */
    public EqualsMethod withNonNullableFields(ElementMatcher nonNullable) {
        return new EqualsMethod(superClassCheck, typeCompatibilityCheck, ignored, this.nonNullable.or(nonNullable));
    }

    /**
     * Returns a new version of this equals method implementation that permits subclasses of the instrumented type to be equal to instances
     * of the instrumented type instead of requiring an exact match.
     *
     * @return A new version of this equals method implementation that permits subclasses of the instrumented type to be equal to instances
     * of the instrumented type instead of requiring an exact match.
     */
    public Implementation withSubclassEquality() {
        return new EqualsMethod(superClassCheck, TypeCompatibilityCheck.SUBCLASS, ignored, nonNullable);
    }

    @Override
    public InstrumentedType prepare(InstrumentedType instrumentedType) {
        return instrumentedType;
    }

    @Override
    public ByteCodeAppender appender(Target implementationTarget) {
        if (implementationTarget.getInstrumentedType().isInterface()) {
            throw new IllegalStateException("Cannot implement meaningful equals method for " + implementationTarget.getInstrumentedType());
        }
        return new Appender(implementationTarget.getInstrumentedType(), new StackManipulation.Compound(
                superClassCheck.resolve(implementationTarget.getInstrumentedType()),
                MethodVariableAccess.loadThis(),
                MethodVariableAccess.REFERENCE.loadFrom(1),
                ConditionalReturn.onIdentity().returningTrue(),
                typeCompatibilityCheck.resolve(implementationTarget.getInstrumentedType())
        ), implementationTarget.getInstrumentedType().getDeclaredFields().filter(not(isStatic().or(ignored))), nonNullable);
    }

    /**
     * Checks the equality contract against the super class.
     */
    protected enum SuperClassCheck {

        /**
         * Does not perform any super class check.
         */
        DISABLED {
            @Override
            protected StackManipulation resolve(TypeDescription instrumentedType) {
                return StackManipulation.Trivial.INSTANCE;
            }
        },

        /**
         * Invokes the super class's {@link Object#equals(Object)} method.
         */
        ENABLED {
            @Override
            protected StackManipulation resolve(TypeDescription instrumentedType) {
                TypeDefinition superClass = instrumentedType.getSuperClass();
                if (superClass == null) {
                    throw new IllegalStateException(instrumentedType + " does not declare a super class");
                }
                return new StackManipulation.Compound(MethodVariableAccess.loadThis(),
                        MethodVariableAccess.REFERENCE.loadFrom(1),
                        MethodInvocation.invoke(EQUALS).special(superClass.asErasure()),
                        ConditionalReturn.onZeroInteger());
            }
        };

        /**
         * Resolves a stack manipulation for the required super class check.
         *
         * @param instrumentedType The instrumented type.
         * @return A stack manipulation that implements the specified check.
         */
        protected abstract StackManipulation resolve(TypeDescription instrumentedType);
    }

    /**
     * Checks the overall type of the provided argument.
     */
    protected enum TypeCompatibilityCheck {

        /**
         * Requires an exact type match.
         */
        EXACT {
            @Override
            public StackManipulation resolve(TypeDescription instrumentedType) {
                return new StackManipulation.Compound(
                        MethodVariableAccess.REFERENCE.loadFrom(1),
                        ConditionalReturn.onNullValue(),
                        MethodVariableAccess.REFERENCE.loadFrom(0),
                        MethodInvocation.invoke(GET_CLASS),
                        MethodVariableAccess.REFERENCE.loadFrom(1),
                        MethodInvocation.invoke(GET_CLASS),
                        ConditionalReturn.onNonIdentity()
                );
            }
        },

        /**
         * Requires a subtype relationship.
         */
        SUBCLASS {
            @Override
            protected StackManipulation resolve(TypeDescription instrumentedType) {
                return new StackManipulation.Compound(
                        MethodVariableAccess.REFERENCE.loadFrom(1),
                        InstanceCheck.of(instrumentedType),
                        ConditionalReturn.onZeroInteger()
                );
            }
        };

        /**
         * The {@link Object#getClass()} method.
         */
        protected static final MethodDescription.InDefinedShape GET_CLASS = TypeDescription.ForLoadedType.of(Object.class)
                .getDeclaredMethods()
                .filter(named("getClass"))
                .getOnly();

        /**
         * Resolves a stack manipulation for the required type compatibility check.
         *
         * @param instrumentedType The instrumented type.
         * @return A stack manipulation that implements the specified check.
         */
        protected abstract StackManipulation resolve(TypeDescription instrumentedType);
    }

    /**
     * Guards a field value against a potential {@code null} value.
     */
    protected interface NullValueGuard {

        /**
         * Returns a stack manipulation to apply before computing equality.
         *
         * @return A stack manipulation to apply before computing equality.
         */
        StackManipulation before();

        /**
         * Returns a stack manipulation to apply after computing equality.
         *
         * @return A stack manipulation to apply after computing equality.
         */
        StackManipulation after();

        /**
         * Returns the required padding for the local variable array to apply this guard.
         *
         * @return The required padding for the local variable array to apply this guard.
         */
        int getRequiredVariablePadding();

        /**
         * A non-operational null value guard.
         */
        enum NoOp implements NullValueGuard {

            /**
             * The singleton instance.
             */
            INSTANCE;

            @Override
            public StackManipulation before() {
                return StackManipulation.Trivial.INSTANCE;
            }

            @Override
            public StackManipulation after() {
                return StackManipulation.Trivial.INSTANCE;
            }

            @Override
            public int getRequiredVariablePadding() {
                return StackSize.ZERO.getSize();
            }
        }

        /**
         * A null value guard that expects a reference type and that skips the comparison if both values are {@code null} but returns if
         * the invoked instance's field value is {@code null} but not the compared instance's value.
         */
        @HashCodeAndEqualsPlugin.Enhance
        class UsingJump implements NullValueGuard {

            /**
             * An empty array.
             */
            private static final Object[] EMPTY = new Object[0];

            /**
             * An array containing a single reference value.
             */
            private static final Object[] REFERENCE = new Object[]{Type.getInternalName(Object.class)};

            /**
             * The instrumented method.
             */
            private final MethodDescription instrumentedMethod;

            /**
             * The label to jump to if the first value is {@code null} whereas the second value is not {@code null}.
             */
            private final Label firstValueNull;

            /**
             * The label to jump to if the second value is {@code null}.
             */
            private final Label secondValueNull;

            /**
             * A label indicating the end of the null-guarding block.
             */
            private final Label endOfBlock;

            /**
             * Creates a new null value guard using a jump instruction for {@code null} values.
             *
             * @param instrumentedMethod The instrumented method.
             */
            protected UsingJump(MethodDescription instrumentedMethod) {
                this.instrumentedMethod = instrumentedMethod;
                firstValueNull = new Label();
                secondValueNull = new Label();
                endOfBlock = new Label();
            }

            @Override
            public StackManipulation before() {
                return new UsingJump.BeforeInstruction();
            }

            @Override
            public StackManipulation after() {
                return new UsingJump.AfterInstruction();
            }

            @Override
            public int getRequiredVariablePadding() {
                return 2;
            }

            /**
             * The stack manipulation to apply before the equality computation.
             */
            @HashCodeAndEqualsPlugin.Enhance(includeSyntheticFields = true)
            protected class BeforeInstruction implements StackManipulation {

                @Override
                public boolean isValid() {
                    return true;
                }

                @Override
                public Size apply(MethodVisitor methodVisitor, Context implementationContext) {
                    methodVisitor.visitVarInsn(Opcodes.ASTORE, instrumentedMethod.getStackSize());
                    methodVisitor.visitVarInsn(Opcodes.ASTORE, instrumentedMethod.getStackSize() + 1);
                    methodVisitor.visitVarInsn(Opcodes.ALOAD, instrumentedMethod.getStackSize() + 1);
                    methodVisitor.visitVarInsn(Opcodes.ALOAD, instrumentedMethod.getStackSize());
                    methodVisitor.visitJumpInsn(Opcodes.IFNULL, secondValueNull);
                    methodVisitor.visitJumpInsn(Opcodes.IFNULL, firstValueNull);
                    methodVisitor.visitVarInsn(Opcodes.ALOAD, instrumentedMethod.getStackSize() + 1);
                    methodVisitor.visitVarInsn(Opcodes.ALOAD, instrumentedMethod.getStackSize());
                    return new Size(0, 0);
                }
            }

            /**
             * The stack manipulation to apply after the equality computation.
             */
            @HashCodeAndEqualsPlugin.Enhance(includeSyntheticFields = true)
            protected class AfterInstruction implements StackManipulation {

                @Override
                public boolean isValid() {
                    return true;
                }

                @Override
                public Size apply(MethodVisitor methodVisitor, Context implementationContext) {
                    methodVisitor.visitJumpInsn(Opcodes.GOTO, endOfBlock);
                    methodVisitor.visitLabel(secondValueNull);
                    if (implementationContext.getClassFileVersion().isAtLeast(ClassFileVersion.JAVA_V6)) {
                        methodVisitor.visitFrame(Opcodes.F_SAME1, EMPTY.length, EMPTY, REFERENCE.length, REFERENCE);
                    }
                    methodVisitor.visitJumpInsn(Opcodes.IFNULL, endOfBlock);
                    methodVisitor.visitLabel(firstValueNull);
                    if (implementationContext.getClassFileVersion().isAtLeast(ClassFileVersion.JAVA_V6)) {
                        methodVisitor.visitFrame(Opcodes.F_SAME, EMPTY.length, EMPTY, EMPTY.length, EMPTY);
                    }
                    methodVisitor.visitInsn(Opcodes.ICONST_0);
                    methodVisitor.visitInsn(Opcodes.IRETURN);
                    methodVisitor.visitLabel(endOfBlock);
                    if (implementationContext.getClassFileVersion().isAtLeast(ClassFileVersion.JAVA_V6)) {
                        methodVisitor.visitFrame(Opcodes.F_SAME, EMPTY.length, EMPTY, EMPTY.length, EMPTY);
                    }
                    return new Size(0, 0);
                }
            }
        }
    }

    /**
     * A value comparator is responsible to compare to values of a given type.
     */
    protected enum ValueComparator implements StackManipulation {

        /**
         * A comparator for a {@code long} value.
         */
        LONG {
            @Override
            public Size apply(MethodVisitor methodVisitor, Context implementationContext) {
                methodVisitor.visitInsn(Opcodes.LCMP);
                return new Size(-2, 0);
            }
        },

        /**
         * A comparator for a {@code float} value.
         */
        FLOAT {
            @Override
            public Size apply(MethodVisitor methodVisitor, Context implementationContext) {
                methodVisitor.visitInsn(Opcodes.FCMPL);
                return new Size(-1, 0);
            }
        },

        /**
         * A comparator for a {@code double} value.
         */
        DOUBLE {
            @Override
            public Size apply(MethodVisitor methodVisitor, Context implementationContext) {
                methodVisitor.visitInsn(Opcodes.DCMPL);
                return new Size(-2, 0);
            }
        },

        /**
         * A comparator for a {@code boolean[]} value.
         */
        BOOLEAN_ARRAY {
            @Override
            public Size apply(MethodVisitor methodVisitor, Context implementationContext) {
                methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "java/util/Arrays", "equals", "([Z[Z)Z", false);
                return new Size(-1, 0);
            }
        },

        /**
         * A comparator for a {@code byte[]} value.
         */
        BYTE_ARRAY {
            @Override
            public Size apply(MethodVisitor methodVisitor, Context implementationContext) {
                methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "java/util/Arrays", "equals", "([B[B)Z", false);
                return new Size(-1, 0);
            }
        },

        /**
         * A comparator for a {@code short[]} value.
         */
        SHORT_ARRAY {
            @Override
            public Size apply(MethodVisitor methodVisitor, Context implementationContext) {
                methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "java/util/Arrays", "equals", "([S[S)Z", false);
                return new Size(-1, 0);
            }
        },

        /**
         * A comparator for a {@code char[]} value.
         */
        CHARACTER_ARRAY {
            @Override
            public Size apply(MethodVisitor methodVisitor, Context implementationContext) {
                methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "java/util/Arrays", "equals", "([C[C)Z", false);
                return new Size(-1, 0);
            }
        },

        /**
         * A comparator for an {@code int[]} value.
         */
        INTEGER_ARRAY {
            @Override
            public Size apply(MethodVisitor methodVisitor, Context implementationContext) {
                methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "java/util/Arrays", "equals", "([I[I)Z", false);
                return new Size(-1, 0);
            }
        },

        /**
         * A comparator for a {@code long[]} value.
         */
        LONG_ARRAY {
            @Override
            public Size apply(MethodVisitor methodVisitor, Context implementationContext) {
                methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "java/util/Arrays", "equals", "([J[J)Z", false);
                return new Size(-1, 0);
            }
        },

        /**
         * A comparator for a {@code float[]} value.
         */
        FLOAT_ARRAY {
            @Override
            public Size apply(MethodVisitor methodVisitor, Context implementationContext) {
                methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "java/util/Arrays", "equals", "([F[F)Z", false);
                return new Size(-1, 0);
            }
        },

        /**
         * A transformer for a {@code double[]} value.
         */
        DOUBLE_ARRAY {
            @Override
            public Size apply(MethodVisitor methodVisitor, Context implementationContext) {
                methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "java/util/Arrays", "equals", "([D[D)Z", false);
                return new Size(-1, 0);
            }
        },

        /**
         * A transformer for a reference array value.
         */
        REFERENCE_ARRAY {
            @Override
            public Size apply(MethodVisitor methodVisitor, Context implementationContext) {
                methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "java/util/Arrays", "equals", "([Ljava/lang/Object;[Ljava/lang/Object;)Z", false);
                return new Size(-1, 0);
            }
        },

        /**
         * A transformer for a nested reference array value.
         */
        NESTED_ARRAY {
            @Override
            public Size apply(MethodVisitor methodVisitor, Context implementationContext) {
                methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "java/util/Arrays", "deepEquals", "([Ljava/lang/Object;[Ljava/lang/Object;)Z", false);
                return new Size(-1, 0);
            }
        };

        /**
         * Resolves a type definition to a equality comparison.
         *
         * @param typeDefinition The type definition to resolve.
         * @return The stack manipulation to apply.
         */
        public static StackManipulation of(TypeDefinition typeDefinition) {
            if (typeDefinition.represents(boolean.class)
                    || typeDefinition.represents(byte.class)
                    || typeDefinition.represents(short.class)
                    || typeDefinition.represents(char.class)
                    || typeDefinition.represents(int.class)) {
                return ConditionalReturn.onNonEqualInteger();
            } else if (typeDefinition.represents(long.class)) {
                return new Compound(LONG, ConditionalReturn.onNonZeroInteger());
            } else if (typeDefinition.represents(float.class)) {
                return new Compound(FLOAT, ConditionalReturn.onNonZeroInteger());
            } else if (typeDefinition.represents(double.class)) {
                return new Compound(DOUBLE, ConditionalReturn.onNonZeroInteger());
            } else if (typeDefinition.represents(boolean[].class)) {
                return new Compound(BOOLEAN_ARRAY, ConditionalReturn.onZeroInteger());
            } else if (typeDefinition.represents(byte[].class)) {
                return new Compound(BYTE_ARRAY, ConditionalReturn.onZeroInteger());
            } else if (typeDefinition.represents(short[].class)) {
                return new Compound(SHORT_ARRAY, ConditionalReturn.onZeroInteger());
            } else if (typeDefinition.represents(char[].class)) {
                return new Compound(CHARACTER_ARRAY, ConditionalReturn.onZeroInteger());
            } else if (typeDefinition.represents(int[].class)) {
                return new Compound(INTEGER_ARRAY, ConditionalReturn.onZeroInteger());
            } else if (typeDefinition.represents(long[].class)) {
                return new Compound(LONG_ARRAY, ConditionalReturn.onZeroInteger());
            } else if (typeDefinition.represents(float[].class)) {
                return new Compound(FLOAT_ARRAY, ConditionalReturn.onZeroInteger());
            } else if (typeDefinition.represents(double[].class)) {
                return new Compound(DOUBLE_ARRAY, ConditionalReturn.onZeroInteger());
            } else if (typeDefinition.isArray()) {
                return new Compound(typeDefinition.getComponentType().isArray()
                        ? NESTED_ARRAY
                        : REFERENCE_ARRAY, ConditionalReturn.onZeroInteger());
            } else {
                return new Compound(MethodInvocation.invoke(EQUALS).virtual(typeDefinition.asErasure()), ConditionalReturn.onZeroInteger());
            }
        }

        @Override
        public boolean isValid() {
            return true;
        }
    }

    /**
     * A byte code appender to implement the {@link EqualsMethod}.
     */
    @HashCodeAndEqualsPlugin.Enhance
    protected static class Appender implements ByteCodeAppender {

        /**
         * The instrumented type.
         */
        private final TypeDescription instrumentedType;

        /**
         * The baseline stack manipulation.
         */
        private final StackManipulation baseline;

        /**
         * A list of fields to use for the comparison.
         */
        private final List fieldDescriptions;

        /**
         * A matcher to determine fields of a reference type that cannot be {@code null}.
         */
        private final ElementMatcher nonNullable;

        /**
         * Creates a new appender.
         *
         * @param instrumentedType  The instrumented type.
         * @param baseline          The baseline stack manipulation.
         * @param fieldDescriptions A list of fields to use for the comparison.
         * @param nonNullable       A matcher to determine fields of a reference type that cannot be {@code null}.
         */
        protected Appender(TypeDescription instrumentedType,
                           StackManipulation baseline,
                           List fieldDescriptions,
                           ElementMatcher nonNullable) {
            this.instrumentedType = instrumentedType;
            this.baseline = baseline;
            this.fieldDescriptions = fieldDescriptions;
            this.nonNullable = nonNullable;
        }

        @Override
        public Size apply(MethodVisitor methodVisitor, Context implementationContext, MethodDescription instrumentedMethod) {
            if (instrumentedMethod.isStatic()) {
                throw new IllegalStateException("Hash code method must not be static: " + instrumentedMethod);
            } else if (instrumentedMethod.getParameters().size() != 1 || instrumentedMethod.getParameters().getOnly().getType().isPrimitive()) {
                throw new IllegalStateException();
            } else if (!instrumentedMethod.getReturnType().represents(boolean.class)) {
                throw new IllegalStateException("Hash code method does not return primitive boolean: " + instrumentedMethod);
            }
            List stackManipulations = new ArrayList(3 + fieldDescriptions.size() * 8);
            stackManipulations.add(baseline);
            int padding = 0;
            for (FieldDescription.InDefinedShape fieldDescription : fieldDescriptions) {
                stackManipulations.add(MethodVariableAccess.loadThis());
                stackManipulations.add(FieldAccess.forField(fieldDescription).read());
                stackManipulations.add(MethodVariableAccess.REFERENCE.loadFrom(1));
                stackManipulations.add(TypeCasting.to(instrumentedType));
                stackManipulations.add(FieldAccess.forField(fieldDescription).read());
                NullValueGuard nullValueGuard = fieldDescription.getType().isPrimitive() || fieldDescription.getType().isArray() || nonNullable.matches(fieldDescription)
                        ? NullValueGuard.NoOp.INSTANCE
                        : new NullValueGuard.UsingJump(instrumentedMethod);
                stackManipulations.add(nullValueGuard.before());
                stackManipulations.add(ValueComparator.of(fieldDescription.getType()));
                stackManipulations.add(nullValueGuard.after());
                padding = Math.max(padding, nullValueGuard.getRequiredVariablePadding());
            }
            stackManipulations.add(IntegerConstant.forValue(true));
            stackManipulations.add(MethodReturn.INTEGER);
            return new Size(new StackManipulation.Compound(stackManipulations).apply(methodVisitor, implementationContext).getMaximalSize(), instrumentedMethod.getStackSize() + padding);
        }
    }

    /**
     * A conditional return aborts the equality computation if a given condition was reached.
     */
    @HashCodeAndEqualsPlugin.Enhance
    protected static class ConditionalReturn implements StackManipulation {

        /**
         * An empty array.
         */
        private static final Object[] EMPTY = new Object[0];

        /**
         * The conditional jump instruction upon which the return is not triggered.
         */
        private final int jumpCondition;

        /**
         * The opcode for the value being returned.
         */
        private final int value;

        /**
         * Creates a conditional return for a value of {@code false}.
         *
         * @param jumpCondition The opcode upon which the return is not triggered.
         */
        protected ConditionalReturn(int jumpCondition) {
            this(jumpCondition, Opcodes.ICONST_0);
        }

        /**
         * Creates a conditional return.
         *
         * @param jumpCondition The opcode upon which the return is not triggered.
         * @param value         The opcode for the value being returned.
         */
        private ConditionalReturn(int jumpCondition, int value) {
            this.jumpCondition = jumpCondition;
            this.value = value;
        }

        /**
         * Returns a conditional return that returns on an {@code int} value of {@code 0}.
         *
         * @return A conditional return that returns on an {@code int} value of {@code 0}.
         */
        protected static ConditionalReturn onZeroInteger() {
            return new ConditionalReturn(Opcodes.IFNE);
        }

        /**
         * Returns a conditional return that returns on an {@code int} value of not {@code 0}.
         *
         * @return A conditional return that returns on an {@code int} value of not {@code 0}.
         */
        protected static ConditionalReturn onNonZeroInteger() {
            return new ConditionalReturn(Opcodes.IFEQ);
        }

        /**
         * Returns a conditional return that returns on a reference value of {@code null}.
         *
         * @return A conditional return that returns on a reference value of {@code null}.
         */
        protected static ConditionalReturn onNullValue() {
            return new ConditionalReturn(Opcodes.IFNONNULL);
        }

        /**
         * Returns a conditional return that returns if two reference values are not identical.
         *
         * @return A conditional return that returns if two reference values are not identical.
         */
        protected static ConditionalReturn onNonIdentity() {
            return new ConditionalReturn(Opcodes.IF_ACMPEQ);
        }

        /**
         * Returns a conditional return that returns if two reference values are identical.
         *
         * @return A conditional return that returns if two reference values are identical.
         */
        protected static ConditionalReturn onIdentity() {
            return new ConditionalReturn(Opcodes.IF_ACMPNE);
        }

        /**
         * Returns a conditional return that returns if two {@code int} values are not equal.
         *
         * @return A conditional return that returns if two {@code int} values are not equal.
         */
        protected static ConditionalReturn onNonEqualInteger() {
            return new ConditionalReturn(Opcodes.IF_ICMPEQ);
        }

        /**
         * Returns a new stack manipulation that returns {@code true} for the given condition.
         *
         * @return A new stack manipulation that returns {@code true} for the given condition.
         */
        protected StackManipulation returningTrue() {
            return new ConditionalReturn(jumpCondition, Opcodes.ICONST_1);
        }

        @Override
        public boolean isValid() {
            return true;
        }

        @Override
        public Size apply(MethodVisitor methodVisitor, Context implementationContext) {
            Label label = new Label();
            methodVisitor.visitJumpInsn(jumpCondition, label);
            methodVisitor.visitInsn(value);
            methodVisitor.visitInsn(Opcodes.IRETURN);
            methodVisitor.visitLabel(label);
            if (implementationContext.getClassFileVersion().isAtLeast(ClassFileVersion.JAVA_V6)) {
                methodVisitor.visitFrame(Opcodes.F_SAME, EMPTY.length, EMPTY, EMPTY.length, EMPTY);
            }
            return new Size(-1, 1);
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy