net.bytebuddy.implementation.EqualsMethod Maven / Gradle / Ivy
The newest version!
/*
* Copyright 2014 - Present Rafael Winterhalter
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.bytebuddy.implementation;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
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 java.util.*;
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.ForLoadedType.of(Object.class)
.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 super FieldDescription.InDefinedShape> ignored;
/**
* A matcher to determine fields of a reference type that cannot be {@code null}.
*/
private final ElementMatcher.Junction super FieldDescription.InDefinedShape> nonNullable;
/**
* The comparator to apply for ordering fields.
*/
private final Comparator super FieldDescription.InDefinedShape> comparator;
/**
* Creates a new equals method implementation.
*
* @param superClassCheck The baseline equality to check.
*/
protected EqualsMethod(SuperClassCheck superClassCheck) {
this(superClassCheck, TypeCompatibilityCheck.EXACT, none(), none(), NaturalOrderComparator.INSTANCE);
}
/**
* 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}.
* @param comparator The comparator to apply for ordering fields.
*/
private EqualsMethod(SuperClassCheck superClassCheck,
TypeCompatibilityCheck typeCompatibilityCheck,
ElementMatcher.Junction super FieldDescription.InDefinedShape> ignored,
ElementMatcher.Junction super FieldDescription.InDefinedShape> nonNullable,
Comparator super FieldDescription.InDefinedShape> comparator) {
this.superClassCheck = superClassCheck;
this.typeCompatibilityCheck = typeCompatibilityCheck;
this.ignored = ignored;
this.nonNullable = nonNullable;
this.comparator = comparator;
}
/**
* 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 super FieldDescription.InDefinedShape> ignored) {
return new EqualsMethod(superClassCheck, typeCompatibilityCheck, this.ignored.or(ignored), nonNullable, comparator);
}
/**
* 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 super FieldDescription.InDefinedShape> nonNullable) {
return new EqualsMethod(superClassCheck, typeCompatibilityCheck, ignored, this.nonNullable.or(nonNullable), comparator);
}
/**
* Returns a new version of this equals method that compares fields with primitive types prior to fields with non-primitive types.
*
* @return A new version of this equals method that compares primitive-typed fields before fields with non-primitive-typed fields.
*/
public EqualsMethod withPrimitiveTypedFieldsFirst() {
return withFieldOrder(TypePropertyComparator.FOR_PRIMITIVE_TYPES);
}
/**
* Returns a new version of this equals method that compares fields with enumeration types prior to fields with non-enumeration types.
*
* @return A new version of this equals method that compares enumeration-typed fields before fields with non-enumeration-typed fields.
*/
public EqualsMethod withEnumerationTypedFieldsFirst() {
return withFieldOrder(TypePropertyComparator.FOR_ENUMERATION_TYPES);
}
/**
* Returns a new version of this equals method that compares fields with primitive wrapper types prior to fields with non-primitive wrapper types.
*
* @return A new version of this equals method that compares primitive wrapper-typed fields before fields with non-primitive wrapper-typed fields.
*/
public EqualsMethod withPrimitiveWrapperTypedFieldsFirst() {
return withFieldOrder(TypePropertyComparator.FOR_PRIMITIVE_WRAPPER_TYPES);
}
/**
* Returns a new version of this equals method that compares fields with {@link String} types prior to fields with non-{@link String} types.
*
* @return A new version of this equals method that compares {@link String}-typed fields before fields with non-{@link String}-typed fields.
*/
public EqualsMethod withStringTypedFieldsFirst() {
return withFieldOrder(TypePropertyComparator.FOR_STRING_TYPES);
}
/**
* Applies the supplied comparator to determine an order for fields for being compared. Fields with the lowest sort order are compared
* first. Any previously defined comparators are applied prior to the supplied comparator.
*
* @param comparator The comparator to apply.
* @return A new version of this equals method that sorts fields in their application order using the supplied comparator.
*/
@SuppressWarnings("unchecked") // In absence of @SafeVarargs
public EqualsMethod withFieldOrder(Comparator super FieldDescription.InDefinedShape> comparator) {
return new EqualsMethod(superClassCheck, typeCompatibilityCheck, ignored, nonNullable, new CompoundComparator(this.comparator, comparator));
}
/**
* 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, comparator);
}
/**
* {@inheritDoc}
*/
public InstrumentedType prepare(InstrumentedType instrumentedType) {
return instrumentedType;
}
/**
* {@inheritDoc}
*/
public ByteCodeAppender appender(Target implementationTarget) {
if (implementationTarget.getInstrumentedType().isInterface()) {
throw new IllegalStateException("Cannot implement meaningful equals method for " + implementationTarget.getInstrumentedType());
}
List fields = new ArrayList(implementationTarget.getInstrumentedType()
.getDeclaredFields()
.filter(not(isStatic().or(ignored))));
Collections.sort(fields, comparator);
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())
), fields, 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;
/**
* {@inheritDoc}
*/
public StackManipulation before() {
return StackManipulation.Trivial.INSTANCE;
}
/**
* {@inheritDoc}
*/
public StackManipulation after() {
return StackManipulation.Trivial.INSTANCE;
}
/**
* {@inheritDoc}
*/
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 {
/**
* 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();
}
/**
* {@inheritDoc}
*/
public StackManipulation before() {
return new UsingJump.BeforeInstruction();
}
/**
* {@inheritDoc}
*/
public StackManipulation after() {
return new UsingJump.AfterInstruction();
}
/**
* {@inheritDoc}
*/
public int getRequiredVariablePadding() {
return 2;
}
/**
* The stack manipulation to apply before the equality computation.
*/
@HashCodeAndEqualsPlugin.Enhance(includeSyntheticFields = true)
protected class BeforeInstruction extends StackManipulation.AbstractBase {
/**
* {@inheritDoc}
*/
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 Size.ZERO;
}
}
/**
* The stack manipulation to apply after the equality computation.
*/
@HashCodeAndEqualsPlugin.Enhance(includeSyntheticFields = true)
protected class AfterInstruction extends StackManipulation.AbstractBase {
/**
* {@inheritDoc}
*/
public Size apply(MethodVisitor methodVisitor, Context implementationContext) {
methodVisitor.visitJumpInsn(Opcodes.GOTO, endOfBlock);
methodVisitor.visitLabel(secondValueNull);
implementationContext.getFrameGeneration().same1(methodVisitor,
TypeDescription.ForLoadedType.of(Object.class),
Arrays.asList(implementationContext.getInstrumentedType(), TypeDescription.ForLoadedType.of(Object.class)));
methodVisitor.visitJumpInsn(Opcodes.IFNULL, endOfBlock);
methodVisitor.visitLabel(firstValueNull);
implementationContext.getFrameGeneration().same(methodVisitor,
Arrays.asList(implementationContext.getInstrumentedType(), TypeDescription.ForLoadedType.of(Object.class)));
methodVisitor.visitInsn(Opcodes.ICONST_0);
methodVisitor.visitInsn(Opcodes.IRETURN);
methodVisitor.visitLabel(endOfBlock);
implementationContext.getFrameGeneration().same(methodVisitor,
Arrays.asList(implementationContext.getInstrumentedType(), TypeDescription.ForLoadedType.of(Object.class)));
return Size.ZERO;
}
}
}
}
/**
* 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 {
/** {@inheritDoc} */
public Size apply(MethodVisitor methodVisitor, Context implementationContext) {
methodVisitor.visitInsn(Opcodes.LCMP);
return new Size(-2, 0);
}
},
/**
* A comparator for a {@code float} value.
*/
FLOAT {
/** {@inheritDoc} */
public Size apply(MethodVisitor methodVisitor, Context implementationContext) {
methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Float", "compare", "(FF)I", false);
return new Size(-1, 0);
}
},
/**
* A comparator for a {@code double} value.
*/
DOUBLE {
/** {@inheritDoc} */
public Size apply(MethodVisitor methodVisitor, Context implementationContext) {
methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Double", "compare", "(DD)I", false);
return new Size(-2, 0);
}
},
/**
* A comparator for a {@code boolean[]} value.
*/
BOOLEAN_ARRAY {
/** {@inheritDoc} */
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 {
/** {@inheritDoc} */
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 {
/** {@inheritDoc} */
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 {
/** {@inheritDoc} */
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 {
/** {@inheritDoc} */
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 {
/** {@inheritDoc} */
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 {
/** {@inheritDoc} */
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 {
/** {@inheritDoc} */
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 {
/** {@inheritDoc} */
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 {
/** {@inheritDoc} */
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.
*/
@SuppressFBWarnings(value = "NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE", justification = "Assuming component type for array type.")
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());
}
}
/**
* {@inheritDoc}
*/
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 super FieldDescription.InDefinedShape> 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 super FieldDescription.InDefinedShape> nonNullable) {
this.instrumentedType = instrumentedType;
this.baseline = baseline;
this.fieldDescriptions = fieldDescriptions;
this.nonNullable = nonNullable;
}
/**
* {@inheritDoc}
*/
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 extends StackManipulation.AbstractBase {
/**
* 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);
}
/**
* {@inheritDoc}
*/
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);
implementationContext.getFrameGeneration().same(methodVisitor,
Arrays.asList(implementationContext.getInstrumentedType(), TypeDescription.ForLoadedType.of(Object.class)));
return new Size(-1, 1);
}
}
/**
* A comparator that retains the natural order.
*/
protected enum NaturalOrderComparator implements Comparator {
/**
* The singleton instance.
*/
INSTANCE;
/**
* {@inheritDoc}
*/
public int compare(FieldDescription.InDefinedShape left, FieldDescription.InDefinedShape right) {
return 0;
}
}
/**
* A comparator that sorts fields by a type property.
*/
protected enum TypePropertyComparator implements Comparator {
/**
* Weights primitive types before non-primitive types.
*/
FOR_PRIMITIVE_TYPES {
@Override
protected boolean resolve(TypeDefinition typeDefinition) {
return typeDefinition.isPrimitive();
}
},
/**
* Weights enumeration types before non-enumeration types.
*/
FOR_ENUMERATION_TYPES {
@Override
protected boolean resolve(TypeDefinition typeDefinition) {
return typeDefinition.isEnum();
}
},
/**
* Weights {@link String} types first.
*/
FOR_STRING_TYPES {
@Override
protected boolean resolve(TypeDefinition typeDefinition) {
return typeDefinition.represents(String.class);
}
},
/**
* Weights primitive wrapper types first.
*/
FOR_PRIMITIVE_WRAPPER_TYPES {
@Override
protected boolean resolve(TypeDefinition typeDefinition) {
return typeDefinition.asErasure().isPrimitiveWrapper();
}
};
/**
* {@inheritDoc}
*/
public int compare(FieldDescription.InDefinedShape left, FieldDescription.InDefinedShape right) {
if (resolve(left.getType()) && !resolve(right.getType())) {
return -1;
} else if (!resolve(left.getType()) && resolve(right.getType())) {
return 1;
} else {
return 0;
}
}
/**
* Resolves a type property.
*
* @param typeDefinition The type to resolve the property for.
* @return {@code true} if the type property is resolved.
*/
protected abstract boolean resolve(TypeDefinition typeDefinition);
}
/**
* A compound comparator that compares the values of multiple fields.
*/
@HashCodeAndEqualsPlugin.Enhance
@SuppressFBWarnings(value = "SE_COMPARATOR_SHOULD_BE_SERIALIZABLE", justification = "Not used within a serializable instance")
protected static class CompoundComparator implements Comparator {
/**
* All comparators to be applied in the application order.
*/
private final List> comparators;
/**
* Creates a compound comparator.
*
* @param comparator All comparators to be applied in the application order.
*/
@SuppressWarnings("unchecked") // In absence of @SafeVarargs
protected CompoundComparator(Comparator super FieldDescription.InDefinedShape>... comparator) {
this(Arrays.asList(comparator));
}
/**
* Creates a compound comparator.
*
* @param comparators All comparators to be applied in the application order.
*/
protected CompoundComparator(List extends Comparator super FieldDescription.InDefinedShape>> comparators) {
this.comparators = new ArrayList>();
for (Comparator super FieldDescription.InDefinedShape> comparator : comparators) {
if (comparator instanceof CompoundComparator) {
this.comparators.addAll(((CompoundComparator) comparator).comparators);
} else if (!(comparator instanceof NaturalOrderComparator)) {
this.comparators.add(comparator);
}
}
}
/**
* {@inheritDoc}
*/
public int compare(FieldDescription.InDefinedShape left, FieldDescription.InDefinedShape right) {
for (Comparator super FieldDescription.InDefinedShape> comparator : comparators) {
int comparison = comparator.compare(left, right);
if (comparison != 0) {
return comparison;
}
}
return 0;
}
}
}