java.net.bytebuddy.implementation.HashCodeMethod Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of securemock Show documentation
Show all versions of securemock Show documentation
Libraries for Elasticsearch
The newest version!
/*
* Copyright 2014 - 2018 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 {@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.
*/
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.
*/
private static final int DEFAULT_MULTIPLIER = 31;
/**
* 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.
*/
private final OffsetProvider offsetProvider;
/**
* 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.
*/
private final ElementMatcher.Junction super FieldDescription.InDefinedShape> ignored;
/**
* A matcher to determine fields of a reference type that cannot be {@code null}.
*/
private final ElementMatcher.Junction super FieldDescription.InDefinedShape> nonNullable;
/**
* 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.
*
* @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.
*
* @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.
*
* @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.
*
* @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.
*
* @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.or(ignored), nonNullable);
}
/**
* 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.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.
*
* @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}
*/
public InstrumentedType prepare(InstrumentedType instrumentedType) {
return instrumentedType;
}
/**
* {@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.
*/
protected interface OffsetProvider {
/**
* 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.
*/
@HashCodeAndEqualsPlugin.Enhance
class ForFixedValue implements OffsetProvider {
/**
* The value to load onto the operand stack.
*/
private final int value;
/**
* 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}
*/
public StackManipulation resolve(TypeDescription instrumentedType) {
return IntegerConstant.forValue(value);
}
}
/**
* An offset provider that invokes the super class's {@link Object#hashCode()} implementation.
*/
enum ForSuperMethodCall implements OffsetProvider {
/**
* The singleton instance.
*/
INSTANCE;
/**
* {@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 {@code null} values for fields with reference types.
*/
protected interface NullValueGuard {
/**
* 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.
*
* @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.
*
* @return The required padding for the local variable array to apply this guard.
*/
int getRequiredVariablePadding();
/**
* A non-operational null value guard.
*/
enum NoOp implements NullValueGuard {
/**
* The singleton instance.
*/
INSTANCE;
/**
* {@inheritDoc}
*/
public StackManipulation before() {
return StackManipulation.Trivial.INSTANCE;
}
/**
* {@inheritDoc}
*/
public StackManipulation after() {
return StackManipulation.Trivial.INSTANCE;
}
/**
* {@inheritDoc}
*/
public int getRequiredVariablePadding() {
return StackSize.ZERO.getSize();
}
}
/**
* A null value guard that expects a reference type and that uses a jump if a field value is {@code null}.
*/
@HashCodeAndEqualsPlugin.Enhance
class UsingJump implements NullValueGuard {
/**
* An empty array.
*/
private static final Object[] EMPTY = new Object[0];
/**
* An array that only contains an integer stack map frame.
*/
private static final Object[] INTEGER = new Object[]{Opcodes.INTEGER};
/**
* The instrumented method.
*/
private final MethodDescription instrumentedMethod;
/**
* A label to indicate the target of a jump.
*/
private final Label label;
/**
* 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}
*/
public StackManipulation before() {
return new BeforeInstruction();
}
/**
* {@inheritDoc}
*/
public StackManipulation after() {
return new AfterInstruction();
}
/**
* {@inheritDoc}
*/
public int getRequiredVariablePadding() {
return 1;
}
/**
* The stack manipulation to apply before the hash value computation.
*/
@HashCodeAndEqualsPlugin.Enhance(includeSyntheticFields = true)
protected class BeforeInstruction implements StackManipulation {
/**
* {@inheritDoc}
*/
public boolean isValid() {
return true;
}
/**
* {@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.
*/
@HashCodeAndEqualsPlugin.Enhance(includeSyntheticFields = true)
protected class AfterInstruction implements StackManipulation {
/**
* {@inheritDoc}
*/
public boolean isValid() {
return true;
}
/**
* {@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 {@code int} value.
*/
protected enum ValueTransformer implements StackManipulation {
/**
* A transformer for a {@code long} value.
*/
LONG {
/** {@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 {@code float} value.
*/
FLOAT {
/** {@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 {@code double} value.
*/
DOUBLE {
/** {@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 {@code boolean[]} value.
*/
BOOLEAN_ARRAY {
/** {@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 {@code byte[]} value.
*/
BYTE_ARRAY {
/** {@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 {@code short[]} value.
*/
SHORT_ARRAY {
/** {@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 {@code char[]} value.
*/
CHARACTER_ARRAY {
/** {@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 {@code int[]} value.
*/
INTEGER_ARRAY {
/** {@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 {@code long[]} value.
*/
LONG_ARRAY {
/** {@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 {@code float[]} value.
*/
FLOAT_ARRAY {
/** {@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 {@code double[]} value.
*/
DOUBLE_ARRAY {
/** {@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.
*/
REFERENCE_ARRAY {
/** {@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.
*/
NESTED_ARRAY {
/** {@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.
*
* @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}
*/
public boolean isValid() {
return true;
}
}
/**
* 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.
*/
private final StackManipulation initialValue;
/**
* 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.
*/
private final List fieldDescriptions;
/**
* A matcher to determine fields of a reference type that cannot be {@code null}.
*/
private final ElementMatcher super FieldDescription.InDefinedShape> nonNullable;
/**
* Creates a new appender 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 fieldDescriptions,
ElementMatcher super FieldDescription.InDefinedShape> nonNullable) {
this.initialValue = initialValue;
this.multiplier = multiplier;
this.fieldDescriptions = fieldDescriptions;
this.nonNullable = nonNullable;
}
/**
* {@inheritDoc}
*/
public Size apply(MethodVisitor methodVisitor, Context implementationContext, MethodDescription instrumentedMethod) {
if (instrumentedMethod.isStatic()) {
throw new IllegalStateException("Hash code method must not be static: " + instrumentedMethod);
} else if (!instrumentedMethod.getReturnType().represents(int.class)) {
throw new IllegalStateException("Hash code method does not return primitive integer: " + instrumentedMethod);
}
List stackManipulations = new ArrayList(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);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy