/*
* 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 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.*;
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 java.util.ArrayList;
import java.util.List;
import static net.bytebuddy.matcher.ElementMatchers.*;
An implementation of Object.hashCode()
that takes a class's declared fields into consideration. A hash code is computed by transforming primitive field types to an int
value and by summing those values up starting from a given offset after multiplying any previous value with a multiplier. Reference values are checked against null
values unless specified otherwise. /**
* An implementation of {@link Object#hashCode()} that takes a class's declared fields into consideration. A hash code is computed by transforming
* primitive field types to an {@code int} value and by summing those values up starting from a given offset after multiplying any previous value
* with a multiplier. Reference values are checked against {@code null} values unless specified otherwise.
*/
@HashCodeAndEqualsPlugin.Enhance
public class HashCodeMethod implements Implementation {
The default offset which should be a prime number.
/**
* The default offset which should be a prime number.
*/
private static final int DEFAULT_OFFSET = 17;
The default multiplier for each value before adding a field's hash code value which should be a prime number.
/**
* The default multiplier for each value before adding a field's hash code value which should be a prime number.
*/
private static final int DEFAULT_MULTIPLIER = 31;
The Object.hashCode()
method. /**
* The {@link Object#hashCode()} method.
*/
private static final MethodDescription.InDefinedShape HASH_CODE = TypeDescription.ForLoadedType.of(Object.class)
.getDeclaredMethods()
.filter(isHashCode())
.getOnly();
The hash code's offset provider.
/**
* The hash code's offset provider.
*/
private final OffsetProvider offsetProvider;
A multiplier for each value before adding a field's hash code value.
/**
* A multiplier for each value before adding a field's hash code value.
*/
private final int multiplier;
A matcher to filter fields that should not be used for a hash codes computation.
/**
* A matcher to filter fields that should not be used for a hash codes computation.
*/
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;
Creates a new hash code method implementation.
Params: - offsetProvider – The hash code's offset provider.
/**
* Creates a new hash code method implementation.
*
* @param offsetProvider The hash code's offset provider.
*/
protected HashCodeMethod(OffsetProvider offsetProvider) {
this(offsetProvider, DEFAULT_MULTIPLIER, none(), none());
}
Creates a new hash code method implementation.
Params: - offsetProvider – The hash code's offset provider.
- multiplier – A multiplier for each value before adding a field's hash code value
- ignored – A matcher to filter fields that should not be used for a hash codes computation.
- nonNullable – A matcher to determine fields of a reference type that cannot be
null
.
/**
* Creates a new hash code method implementation.
*
* @param offsetProvider The hash code's offset provider.
* @param multiplier A multiplier for each value before adding a field's hash code value
* @param ignored A matcher to filter fields that should not be used for a hash codes computation.
* @param nonNullable A matcher to determine fields of a reference type that cannot be {@code null}.
*/
private HashCodeMethod(OffsetProvider offsetProvider,
int multiplier,
ElementMatcher.Junction<? super FieldDescription.InDefinedShape> ignored,
ElementMatcher.Junction<? super FieldDescription.InDefinedShape> nonNullable) {
this.offsetProvider = offsetProvider;
this.multiplier = multiplier;
this.ignored = ignored;
this.nonNullable = nonNullable;
}
Creates a hash code method implementation that bases the hash code on the instrumented type's super class's hash code value.
Returns: A hash code method implementation that bases the hash code on the instrumented type's super class's hash code value.
/**
* Creates a hash code method implementation that bases the hash code on the instrumented type's super class's hash code value.
*
* @return A hash code method implementation that bases the hash code on the instrumented type's super class's hash code value.
*/
public static HashCodeMethod usingSuperClassOffset() {
return new HashCodeMethod(OffsetProvider.ForSuperMethodCall.INSTANCE);
}
Creates a hash code method implementation that bases the hash code on a fixed value.
Returns: A hash code method implementation that bases the hash code on a fixed value.
/**
* Creates a hash code method implementation that bases the hash code on a fixed value.
*
* @return A hash code method implementation that bases the hash code on a fixed value.
*/
public static HashCodeMethod usingDefaultOffset() {
return usingOffset(DEFAULT_OFFSET);
}
Creates a hash code method implementation that bases the hash code on a fixed value.
Params: - value – The fixed value.
Returns: A hash code method implementation that bases the hash code on a fixed value.
/**
* Creates a hash code method implementation that bases the hash code on a fixed value.
*
* @param value The fixed value.
* @return A hash code method implementation that bases the hash code on a fixed value.
*/
public static HashCodeMethod usingOffset(int value) {
return new HashCodeMethod(new OffsetProvider.ForFixedValue(value));
}
Returns a new version of this hash code 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 hash code method implementation that also ignores any fields matched by the provided matcher.
/**
* Returns a new version of this hash code 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 hash code method implementation that also ignores any fields matched by the provided matcher.
*/
public HashCodeMethod withIgnoredFields(ElementMatcher<? super FieldDescription.InDefinedShape> ignored) {
return new HashCodeMethod(offsetProvider, multiplier, this.ignored.<FieldDescription.InDefinedShape>or(ignored), nonNullable);
}
Returns a new version of this hash code 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 hash code method implementation that also does not apply null
value checks to any fields matched by the provided matcher.
/**
* Returns a new version of this hash code 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 hash code method implementation that also does not apply {@code null} value checks to any fields matched by
* the provided matcher.
*/
public HashCodeMethod withNonNullableFields(ElementMatcher<? super FieldDescription.InDefinedShape> nonNullable) {
return new HashCodeMethod(offsetProvider, multiplier, ignored, this.nonNullable.<FieldDescription.InDefinedShape>or(nonNullable));
}
Returns a new version of this hash code method implementation that uses the given multiplier onto any given hash code before adding a
field's hash code.
Params: - multiplier – The multiplier to use for any hash code before adding any field's hash code.
Returns: A new version of this hash code method implementation that uses the given multiplier onto any given hash code before adding a
field's hash code.
/**
* Returns a new version of this hash code method implementation that uses the given multiplier onto any given hash code before adding a
* field's hash code.
*
* @param multiplier The multiplier to use for any hash code before adding any field's hash code.
* @return A new version of this hash code method implementation that uses the given multiplier onto any given hash code before adding a
* field's hash code.
*/
public Implementation withMultiplier(int multiplier) {
if (multiplier == 0) {
throw new IllegalArgumentException("Hash code multiplier must not be zero");
}
return new HashCodeMethod(offsetProvider, multiplier, ignored, nonNullable);
}
{@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 hash code method for " + implementationTarget.getInstrumentedType());
}
return new Appender(offsetProvider.resolve(implementationTarget.getInstrumentedType()),
multiplier,
implementationTarget.getInstrumentedType().getDeclaredFields().filter(not(isStatic().or(ignored))),
nonNullable);
}
An offset provider is responsible for supplying the initial hash code.
/**
* An offset provider is responsible for supplying the initial hash code.
*/
protected interface OffsetProvider {
Resolves this offset provider for a given instrumented type.
Params: - instrumentedType – The instrumented type.
Returns: A stack manipulation that loads the initial hash code onto the operand stack.
/**
* Resolves this offset provider for a given instrumented type.
*
* @param instrumentedType The instrumented type.
* @return A stack manipulation that loads the initial hash code onto the operand stack.
*/
StackManipulation resolve(TypeDescription instrumentedType);
An offset provider that supplies a fixed value.
/**
* An offset provider that supplies a fixed value.
*/
@HashCodeAndEqualsPlugin.Enhance
class ForFixedValue implements OffsetProvider {
The value to load onto the operand stack.
/**
* The value to load onto the operand stack.
*/
private final int value;
Creates a new offset provider for a fixed value.
Params: - value – The value to load onto the operand stack.
/**
* Creates a new offset provider for a fixed value.
*
* @param value The value to load onto the operand stack.
*/
protected ForFixedValue(int value) {
this.value = value;
}
{@inheritDoc}
/**
* {@inheritDoc}
*/
public StackManipulation resolve(TypeDescription instrumentedType) {
return IntegerConstant.forValue(value);
}
}
An offset provider that invokes the super class's Object.hashCode()
implementation. /**
* An offset provider that invokes the super class's {@link Object#hashCode()} implementation.
*/
enum ForSuperMethodCall implements OffsetProvider {
The singleton instance.
/**
* The singleton instance.
*/
INSTANCE;
{@inheritDoc}
/**
* {@inheritDoc}
*/
public 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(), MethodInvocation.invoke(HASH_CODE).special(superClass.asErasure()));
}
}
}
A guard against null
values for fields with reference types. /**
* A guard against {@code null} values for fields with reference types.
*/
protected interface NullValueGuard {
Returns a stack manipulation to apply before computing a hash value.
Returns: A stack manipulation to apply before computing a hash value.
/**
* Returns a stack manipulation to apply before computing a hash value.
*
* @return A stack manipulation to apply before computing a hash value.
*/
StackManipulation before();
Returns a stack manipulation to apply after computing a hash value.
Returns: A stack manipulation to apply after computing a hash value.
/**
* Returns a stack manipulation to apply after computing a hash value.
*
* @return A stack manipulation to apply after computing a hash value.
*/
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 uses a jump if a field value is null
. /**
* A null value guard that expects a reference type and that uses a jump if a field value is {@code null}.
*/
@HashCodeAndEqualsPlugin.Enhance
class UsingJump implements NullValueGuard {
An empty array.
/**
* An empty array.
*/
private static final Object[] EMPTY = new Object[0];
An array that only contains an integer stack map frame.
/**
* An array that only contains an integer stack map frame.
*/
private static final Object[] INTEGER = new Object[]{Opcodes.INTEGER};
The instrumented method.
/**
* The instrumented method.
*/
private final MethodDescription instrumentedMethod;
A label to indicate the target of a jump.
/**
* A label to indicate the target of a jump.
*/
private final Label label;
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;
label = new Label();
}
{@inheritDoc}
/**
* {@inheritDoc}
*/
public StackManipulation before() {
return new BeforeInstruction();
}
{@inheritDoc}
/**
* {@inheritDoc}
*/
public StackManipulation after() {
return new AfterInstruction();
}
{@inheritDoc}
/**
* {@inheritDoc}
*/
public int getRequiredVariablePadding() {
return 1;
}
The stack manipulation to apply before the hash value computation.
/**
* The stack manipulation to apply before the hash value 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.ALOAD, instrumentedMethod.getStackSize());
methodVisitor.visitJumpInsn(Opcodes.IFNULL, label);
methodVisitor.visitVarInsn(Opcodes.ALOAD, instrumentedMethod.getStackSize());
return new Size(0, 0);
}
}
The stack manipulation to apply after the hash value computation.
/**
* The stack manipulation to apply after the hash value 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.visitLabel(label);
if (implementationContext.getClassFileVersion().isAtLeast(ClassFileVersion.JAVA_V6)) {
methodVisitor.visitFrame(Opcodes.F_SAME1, EMPTY.length, EMPTY, INTEGER.length, INTEGER);
}
return new Size(0, 0);
}
}
}
}
A value transformer that is responsible for resolving a field value to an int
value. /**
* A value transformer that is responsible for resolving a field value to an {@code int} value.
*/
protected enum ValueTransformer implements StackManipulation {
A transformer for a long
value. /**
* A transformer for a {@code long} value.
*/
LONG {
{@inheritDoc} /** {@inheritDoc} */
public Size apply(MethodVisitor methodVisitor, Context implementationContext) {
methodVisitor.visitInsn(Opcodes.DUP2);
methodVisitor.visitIntInsn(Opcodes.BIPUSH, 32);
methodVisitor.visitInsn(Opcodes.LUSHR);
methodVisitor.visitInsn(Opcodes.LXOR);
methodVisitor.visitInsn(Opcodes.L2I);
return new Size(-1, 3);
}
},
A transformer for a float
value. /**
* A transformer for a {@code float} value.
*/
FLOAT {
{@inheritDoc} /** {@inheritDoc} */
public Size apply(MethodVisitor methodVisitor, Context implementationContext) {
methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Float", "floatToIntBits", "(F)I", false);
return new Size(0, 0);
}
},
A transformer for a double
value. /**
* A transformer for a {@code double} value.
*/
DOUBLE {
{@inheritDoc} /** {@inheritDoc} */
public Size apply(MethodVisitor methodVisitor, Context implementationContext) {
methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Double", "doubleToLongBits", "(D)J", false);
methodVisitor.visitInsn(Opcodes.DUP2);
methodVisitor.visitIntInsn(Opcodes.BIPUSH, 32);
methodVisitor.visitInsn(Opcodes.LUSHR);
methodVisitor.visitInsn(Opcodes.LXOR);
methodVisitor.visitInsn(Opcodes.L2I);
return new Size(-1, 3);
}
},
A transformer for a boolean[]
value. /**
* A transformer for a {@code boolean[]} value.
*/
BOOLEAN_ARRAY {
{@inheritDoc} /** {@inheritDoc} */
public Size apply(MethodVisitor methodVisitor, Context implementationContext) {
methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "java/util/Arrays", "hashCode", "([Z)I", false);
return new Size(0, 0);
}
},
A transformer for a byte[]
value. /**
* A transformer for a {@code byte[]} value.
*/
BYTE_ARRAY {
{@inheritDoc} /** {@inheritDoc} */
public Size apply(MethodVisitor methodVisitor, Context implementationContext) {
methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "java/util/Arrays", "hashCode", "([B)I", false);
return new Size(0, 0);
}
},
A transformer for a short[]
value. /**
* A transformer for a {@code short[]} value.
*/
SHORT_ARRAY {
{@inheritDoc} /** {@inheritDoc} */
public Size apply(MethodVisitor methodVisitor, Context implementationContext) {
methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "java/util/Arrays", "hashCode", "([S)I", false);
return new Size(0, 0);
}
},
A transformer for a char[]
value. /**
* A transformer for a {@code char[]} value.
*/
CHARACTER_ARRAY {
{@inheritDoc} /** {@inheritDoc} */
public Size apply(MethodVisitor methodVisitor, Context implementationContext) {
methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "java/util/Arrays", "hashCode", "([C)I", false);
return new Size(0, 0);
}
},
A transformer for an int[]
value. /**
* A transformer for an {@code int[]} value.
*/
INTEGER_ARRAY {
{@inheritDoc} /** {@inheritDoc} */
public Size apply(MethodVisitor methodVisitor, Context implementationContext) {
methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "java/util/Arrays", "hashCode", "([I)I", false);
return new Size(0, 0);
}
},
A transformer for a long[]
value. /**
* A transformer for a {@code long[]} value.
*/
LONG_ARRAY {
{@inheritDoc} /** {@inheritDoc} */
public Size apply(MethodVisitor methodVisitor, Context implementationContext) {
methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "java/util/Arrays", "hashCode", "([J)I", false);
return new Size(0, 0);
}
},
A transformer for a float[]
value. /**
* A transformer for a {@code float[]} value.
*/
FLOAT_ARRAY {
{@inheritDoc} /** {@inheritDoc} */
public Size apply(MethodVisitor methodVisitor, Context implementationContext) {
methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "java/util/Arrays", "hashCode", "([F)I", false);
return new Size(0, 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", "hashCode", "([D)I", false);
return new Size(0, 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", "hashCode", "([Ljava/lang/Object;)I", false);
return new Size(0, 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", "deepHashCode", "([Ljava/lang/Object;)I", false);
return new Size(0, 0);
}
};
Resolves a type definition to a hash code.
Params: - typeDefinition – The type definition to resolve.
Returns: The stack manipulation to apply.
/**
* Resolves a type definition to a hash code.
*
* @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 Trivial.INSTANCE;
} else if (typeDefinition.represents(long.class)) {
return LONG;
} else if (typeDefinition.represents(float.class)) {
return FLOAT;
} else if (typeDefinition.represents(double.class)) {
return DOUBLE;
} else if (typeDefinition.represents(boolean[].class)) {
return BOOLEAN_ARRAY;
} else if (typeDefinition.represents(byte[].class)) {
return BYTE_ARRAY;
} else if (typeDefinition.represents(short[].class)) {
return SHORT_ARRAY;
} else if (typeDefinition.represents(char[].class)) {
return CHARACTER_ARRAY;
} else if (typeDefinition.represents(int[].class)) {
return INTEGER_ARRAY;
} else if (typeDefinition.represents(long[].class)) {
return LONG_ARRAY;
} else if (typeDefinition.represents(float[].class)) {
return FLOAT_ARRAY;
} else if (typeDefinition.represents(double[].class)) {
return DOUBLE_ARRAY;
} else if (typeDefinition.isArray()) {
return typeDefinition.getComponentType().isArray()
? NESTED_ARRAY
: REFERENCE_ARRAY;
} else {
return MethodInvocation.invoke(HASH_CODE).virtual(typeDefinition.asErasure());
}
}
{@inheritDoc}
/**
* {@inheritDoc}
*/
public boolean isValid() {
return true;
}
}
A byte code appender to implement a hash code method.
/**
* A byte code appender to implement a hash code method.
*/
@HashCodeAndEqualsPlugin.Enhance
protected static class Appender implements ByteCodeAppender {
Loads the initial hash code onto the operand stack.
/**
* Loads the initial hash code onto the operand stack.
*/
private final StackManipulation initialValue;
A multiplier for each value before adding a field's hash code value.
/**
* A multiplier for each value before adding a field's hash code value.
*/
private final int multiplier;
A list of fields to include in the hash code computation.
/**
* A list of fields to include in the hash code computation.
*/
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 for implementing a hash code method.
Params: - initialValue – Loads the initial hash code onto the operand stack.
- multiplier – A multiplier for each value before adding a field's hash code value.
- fieldDescriptions – A list of fields to include in the hash code computation.
- nonNullable – A matcher to determine fields of a reference type that cannot be
null
.
/**
* Creates a new appender for implementing a hash code method.
*
* @param initialValue Loads the initial hash code onto the operand stack.
* @param multiplier A multiplier for each value before adding a field's hash code value.
* @param fieldDescriptions A list of fields to include in the hash code computation.
* @param nonNullable A matcher to determine fields of a reference type that cannot be {@code null}.
*/
protected Appender(StackManipulation initialValue,
int multiplier,
List<FieldDescription.InDefinedShape> fieldDescriptions,
ElementMatcher<? super FieldDescription.InDefinedShape> nonNullable) {
this.initialValue = initialValue;
this.multiplier = multiplier;
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.getReturnType().represents(int.class)) {
throw new IllegalStateException("Hash code method does not return primitive integer: " + instrumentedMethod);
}
List<StackManipulation> stackManipulations = new ArrayList<StackManipulation>(2 + fieldDescriptions.size() * 8);
stackManipulations.add(initialValue);
int padding = 0;
for (FieldDescription.InDefinedShape fieldDescription : fieldDescriptions) {
stackManipulations.add(IntegerConstant.forValue(multiplier));
stackManipulations.add(Multiplication.INTEGER);
stackManipulations.add(MethodVariableAccess.loadThis());
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(ValueTransformer.of(fieldDescription.getType()));
stackManipulations.add(Addition.INTEGER);
stackManipulations.add(nullValueGuard.after());
padding = Math.max(padding, nullValueGuard.getRequiredVariablePadding());
}
stackManipulations.add(MethodReturn.INTEGER);
return new Size(new StackManipulation.Compound(stackManipulations).apply(methodVisitor, implementationContext).getMaximalSize(), instrumentedMethod.getStackSize() + padding);
}
}
}