/*
* Copyright 2014 - 2020 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.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 org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import java.util.*;
import static net.bytebuddy.matcher.ElementMatchers.*;
An implementation of 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 null
or where the field value of the instance upon which the method is invoked returns true
upon calling the value's equals
method. For arrays, the corresponding utilities of Arrays
are used. /**
* 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 Object.equals(Object)
method. /**
* The {@link Object#equals(Object)} method.
*/
private static final MethodDescription.InDefinedShape EQUALS = TypeDescription.OBJECT
.getDeclaredMethods()
.filter(isEquals())
.getOnly();
The baseline equality to check.
/**
* The baseline equality to check.
*/
private final SuperClassCheck superClassCheck;
The instance type compatibility check.
/**
* The instance type compatibility check.
*/
private final TypeCompatibilityCheck typeCompatibilityCheck;
A matcher to filter fields that should not be used for a equality resolution.
/**
* 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 null
. /**
* 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.
/**
* The comparator to apply for ordering fields.
*/
private final Comparator<? super FieldDescription.InDefinedShape> comparator;
Creates a new equals method implementation.
Params: - superClassCheck – The baseline equality to check.
/**
* 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.
Params: - superClassCheck – The baseline equality to check.
- typeCompatibilityCheck – The instance type compatibility check.
- ignored – A matcher to filter fields that should not be used for a equality resolution.
- nonNullable – A matcher to determine fields of a reference type that cannot be
null
. - comparator – The comparator to apply for ordering fields.
/**
* 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 Object.equals(Object)
method first. Returns: An equals implementation that invokes the super class's Object.equals(Object)
method first.
/**
* 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 Object.equals(Object)
method. Returns: An equals method implementation that does not invoke the super class's Object.equals(Object)
method.
/**
* 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.
Params: - ignored – A matcher to specify any fields that should be ignored.
Returns: A new version of this equals method implementation that also ignores any fields matched by the provided matcher.
/**
* 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.<FieldDescription.InDefinedShape>or(ignored), nonNullable, comparator);
}
Returns a new version of this equals method implementation that does not apply a null
value check for the specified fields if they have a reference type additionally to any previously specified fields. Params: - nonNullable – A matcher to specify any fields that should not be guarded against
null
values.
Returns: A new version of this equals method implementation that also does not apply null
value checks to any fields matched by the provided matcher.
/**
* 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.<FieldDescription.InDefinedShape>or(nonNullable), comparator);
}
Returns a new version of this equals method that compares fields with primitive types prior to fields with non-primitive types.
Returns: A new version of this equals method that compares primitive-typed fields before fields with non-primitive-typed fields.
/**
* 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.
Returns: A new version of this equals method that compares enumeration-typed fields before fields with non-enumeration-typed fields.
/**
* 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.
Returns: A new version of this equals method that compares primitive wrapper-typed fields before fields with non-primitive wrapper-typed fields.
/**
* 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 String
types prior to fields with non-String
types. Returns: A new version of this equals method that compares String
-typed fields before fields with non-String
-typed fields.
/**
* 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.
Params: - comparator – The comparator to apply.
Returns: A new version of this equals method that sorts fields in their application order using the supplied comparator.
/**
* 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.
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.
/**
* 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}
/**
* {@inheritDoc}
*/
public InstrumentedType prepare(InstrumentedType instrumentedType) {
return instrumentedType;
}
{@inheritDoc}
/**
* {@inheritDoc}
*/
public ByteCodeAppender appender(Target implementationTarget) {
if (implementationTarget.getInstrumentedType().isInterface()) {
throw new IllegalStateException("Cannot implement meaningful equals method for " + implementationTarget.getInstrumentedType());
}
List<FieldDescription.InDefinedShape> fields = new ArrayList<FieldDescription.InDefinedShape>(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.
/**
* Checks the equality contract against the super class.
*/
protected enum SuperClassCheck {
Does not perform any super class check.
/**
* Does not perform any super class check.
*/
DISABLED {
@Override
protected StackManipulation resolve(TypeDescription instrumentedType) {
return StackManipulation.Trivial.INSTANCE;
}
},
Invokes the super class's Object.equals(Object)
method. /**
* 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.
Params: - instrumentedType – The instrumented type.
Returns: A stack manipulation that implements the specified check.
/**
* 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.
/**
* Checks the overall type of the provided argument.
*/
protected enum TypeCompatibilityCheck {
Requires an exact type match.
/**
* 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.
/**
* 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 Object.getClass()
method. /**
* 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.
Params: - instrumentedType – The instrumented type.
Returns: A stack manipulation that implements the specified check.
/**
* 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 null
value. /**
* Guards a field value against a potential {@code null} value.
*/
protected interface NullValueGuard {
Returns a stack manipulation to apply before computing equality.
Returns: A stack manipulation to apply before computing equality.
/**
* 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.
Returns: A stack manipulation to apply after computing equality.
/**
* 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.
Returns: The required padding for the local variable array to apply this guard.
/**
* 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.
/**
* A non-operational null value guard.
*/
enum NoOp implements NullValueGuard {
The singleton instance.
/**
* The singleton instance.
*/
INSTANCE;
{@inheritDoc}
/**
* {@inheritDoc}
*/
public StackManipulation before() {
return StackManipulation.Trivial.INSTANCE;
}
{@inheritDoc}
/**
* {@inheritDoc}
*/
public StackManipulation after() {
return StackManipulation.Trivial.INSTANCE;
}
{@inheritDoc}
/**
* {@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 null
but returns if the invoked instance's field value is null
but not the compared instance's value. /**
* 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.
/**
* An empty array.
*/
private static final Object[] EMPTY = new Object[0];
An array containing a single reference value.
/**
* An array containing a single reference value.
*/
private static final Object[] REFERENCE = new Object[]{Type.getInternalName(Object.class)};
The instrumented method.
/**
* The instrumented method.
*/
private final MethodDescription instrumentedMethod;
The label to jump to if the first value is null
whereas the second value is not null
. /**
* 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 null
. /**
* 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.
/**
* 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 null
values. Params: - instrumentedMethod – The instrumented method.
/**
* 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}
/**
* {@inheritDoc}
*/
public StackManipulation before() {
return new UsingJump.BeforeInstruction();
}
{@inheritDoc}
/**
* {@inheritDoc}
*/
public StackManipulation after() {
return new UsingJump.AfterInstruction();
}
{@inheritDoc}
/**
* {@inheritDoc}
*/
public int getRequiredVariablePadding() {
return 2;
}
The stack manipulation to apply before the equality computation.
/**
* The stack manipulation to apply before the equality computation.
*/
@HashCodeAndEqualsPlugin.Enhance(includeSyntheticFields = true)
protected class BeforeInstruction implements StackManipulation {
{@inheritDoc}
/**
* {@inheritDoc}
*/
public boolean isValid() {
return true;
}
{@inheritDoc}
/**
* {@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 new Size(0, 0);
}
}
The stack manipulation to apply after the equality computation.
/**
* The stack manipulation to apply after the equality computation.
*/
@HashCodeAndEqualsPlugin.Enhance(includeSyntheticFields = true)
protected class AfterInstruction implements StackManipulation {
{@inheritDoc}
/**
* {@inheritDoc}
*/
public boolean isValid() {
return true;
}
{@inheritDoc}
/**
* {@inheritDoc}
*/
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.
/**
* A value comparator is responsible to compare to values of a given type.
*/
protected enum ValueComparator implements StackManipulation {
A comparator for a long
value. /**
* A comparator for a {@code long} value.
*/
LONG {
{@inheritDoc} /** {@inheritDoc} */
public Size apply(MethodVisitor methodVisitor, Context implementationContext) {
methodVisitor.visitInsn(Opcodes.LCMP);
return new Size(-2, 0);
}
},
A comparator for a float
value. /**
* A comparator for a {@code float} value.
*/
FLOAT {
{@inheritDoc} /** {@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 double
value. /**
* A comparator for a {@code double} value.
*/
DOUBLE {
{@inheritDoc} /** {@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 boolean[]
value. /**
* A comparator for a {@code boolean[]} value.
*/
BOOLEAN_ARRAY {
{@inheritDoc} /** {@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 byte[]
value. /**
* A comparator for a {@code byte[]} value.
*/
BYTE_ARRAY {
{@inheritDoc} /** {@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 short[]
value. /**
* A comparator for a {@code short[]} value.
*/
SHORT_ARRAY {
{@inheritDoc} /** {@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 char[]
value. /**
* A comparator for a {@code char[]} value.
*/
CHARACTER_ARRAY {
{@inheritDoc} /** {@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 int[]
value. /**
* A comparator for an {@code int[]} value.
*/
INTEGER_ARRAY {
{@inheritDoc} /** {@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 long[]
value. /**
* A comparator for a {@code long[]} value.
*/
LONG_ARRAY {
{@inheritDoc} /** {@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 float[]
value. /**
* A comparator for a {@code float[]} value.
*/
FLOAT_ARRAY {
{@inheritDoc} /** {@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 double[]
value. /**
* A transformer for a {@code double[]} value.
*/
DOUBLE_ARRAY {
{@inheritDoc} /** {@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.
/**
* A transformer for a reference array value.
*/
REFERENCE_ARRAY {
{@inheritDoc} /** {@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.
/**
* A transformer for a nested reference array value.
*/
NESTED_ARRAY {
{@inheritDoc} /** {@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.
Params: - typeDefinition – The type definition to resolve.
Returns: The stack manipulation to apply.
/**
* 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());
}
}
{@inheritDoc}
/**
* {@inheritDoc}
*/
public boolean isValid() {
return true;
}
}
A byte code appender to implement the EqualsMethod
. /**
* A byte code appender to implement the {@link EqualsMethod}.
*/
@HashCodeAndEqualsPlugin.Enhance
protected static class Appender implements ByteCodeAppender {
The instrumented type.
/**
* The instrumented type.
*/
private final TypeDescription instrumentedType;
The baseline stack manipulation.
/**
* The baseline stack manipulation.
*/
private final StackManipulation baseline;
A list of fields to use for the comparison.
/**
* A list of fields to use for the comparison.
*/
private final List<FieldDescription.InDefinedShape> fieldDescriptions;
A matcher to determine fields of a reference type that cannot be null
. /**
* 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.
Params: - instrumentedType – The instrumented type.
- baseline – The baseline stack manipulation.
- fieldDescriptions – A list of fields to use for the comparison.
- nonNullable – A matcher to determine fields of a reference type that cannot be
null
.
/**
* 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<FieldDescription.InDefinedShape> fieldDescriptions,
ElementMatcher<? super FieldDescription.InDefinedShape> nonNullable) {
this.instrumentedType = instrumentedType;
this.baseline = baseline;
this.fieldDescriptions = fieldDescriptions;
this.nonNullable = nonNullable;
}
{@inheritDoc}
/**
* {@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<StackManipulation> stackManipulations = new ArrayList<StackManipulation>(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.
/**
* A conditional return aborts the equality computation if a given condition was reached.
*/
@HashCodeAndEqualsPlugin.Enhance
protected static class ConditionalReturn implements StackManipulation {
An empty array.
/**
* An empty array.
*/
private static final Object[] EMPTY = new Object[0];
The conditional jump instruction upon which the return is not triggered.
/**
* The conditional jump instruction upon which the return is not triggered.
*/
private final int jumpCondition;
The opcode for the value being returned.
/**
* The opcode for the value being returned.
*/
private final int value;
Creates a conditional return for a value of false
. Params: - jumpCondition – The opcode upon which the return is not triggered.
/**
* 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.
Params: - jumpCondition – The opcode upon which the return is not triggered.
- value – The opcode for the value being returned.
/**
* 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 int
value of 0
. Returns: A conditional return that returns on an int
value of 0
.
/**
* 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 int
value of not 0
. Returns: A conditional return that returns on an int
value of not 0
.
/**
* 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 null
. Returns: A conditional return that returns on a reference value of null
.
/**
* 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.
Returns: A conditional return that returns if two reference values are not identical.
/**
* 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.
Returns: A conditional return that returns if two reference values are identical.
/**
* 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 int
values are not equal. Returns: A conditional return that returns if two int
values are not equal.
/**
* 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 true
for the given condition. Returns: A new stack manipulation that returns true
for the given condition.
/**
* 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}
/**
* {@inheritDoc}
*/
public boolean isValid() {
return true;
}
{@inheritDoc}
/**
* {@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);
if (implementationContext.getClassFileVersion().isAtLeast(ClassFileVersion.JAVA_V6)) {
methodVisitor.visitFrame(Opcodes.F_SAME, EMPTY.length, EMPTY, EMPTY.length, EMPTY);
}
return new Size(-1, 1);
}
}
A comparator that retains the natural order.
/**
* A comparator that retains the natural order.
*/
protected enum NaturalOrderComparator implements Comparator<FieldDescription.InDefinedShape> {
The singleton instance.
/**
* The singleton instance.
*/
INSTANCE;
{@inheritDoc}
/**
* {@inheritDoc}
*/
public int compare(FieldDescription.InDefinedShape left, FieldDescription.InDefinedShape right) {
return 0;
}
}
A comparator that sorts fields by a type property.
/**
* A comparator that sorts fields by a type property.
*/
protected enum TypePropertyComparator implements Comparator<FieldDescription.InDefinedShape> {
Weights primitive types before non-primitive types.
/**
* 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.
/**
* Weights enumeration types before non-enumeration types.
*/
FOR_ENUMERATION_TYPES {
@Override
protected boolean resolve(TypeDefinition typeDefinition) {
return typeDefinition.isEnum();
}
},
Weights String
types first. /**
* Weights {@link String} types first.
*/
FOR_STRING_TYPES {
@Override
protected boolean resolve(TypeDefinition typeDefinition) {
return typeDefinition.represents(String.class);
}
},
Weights primitive wrapper types first.
/**
* Weights primitive wrapper types first.
*/
FOR_PRIMITIVE_WRAPPER_TYPES {
@Override
protected boolean resolve(TypeDefinition typeDefinition) {
return typeDefinition.asErasure().isPrimitiveWrapper();
}
};
{@inheritDoc}
/**
* {@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.
Params: - typeDefinition – The type to resolve the property for.
Returns: true
if the type property is resolved.
/**
* 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.
/**
* 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<FieldDescription.InDefinedShape> {
All comparators to be applied in the application order.
/**
* All comparators to be applied in the application order.
*/
private final List<Comparator<? super FieldDescription.InDefinedShape>> comparators;
Creates a compound comparator.
Params: - comparator – All comparators to be applied in the application order.
/**
* 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.
Params: - comparators – All comparators to be applied in the application order.
/**
* 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<Comparator<? super FieldDescription.InDefinedShape>>();
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}
/**
* {@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;
}
}
}