/*
 * 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); } } }