net.bytebuddy.implementation.FieldAccessor Maven / Gradle / Ivy
/*
* Copyright 2014 - Present Rafael Winterhalter
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.bytebuddy.implementation;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import net.bytebuddy.build.HashCodeAndEqualsPlugin;
import net.bytebuddy.description.field.FieldDescription;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.dynamic.scaffold.FieldLocator;
import net.bytebuddy.dynamic.scaffold.InstrumentedType;
import net.bytebuddy.implementation.bytecode.ByteCodeAppender;
import net.bytebuddy.implementation.bytecode.StackManipulation;
import net.bytebuddy.implementation.bytecode.assign.Assigner;
import net.bytebuddy.implementation.bytecode.constant.ClassConstant;
import net.bytebuddy.implementation.bytecode.constant.DefaultValue;
import net.bytebuddy.implementation.bytecode.member.FieldAccess;
import net.bytebuddy.implementation.bytecode.member.MethodReturn;
import net.bytebuddy.implementation.bytecode.member.MethodVariableAccess;
import net.bytebuddy.utility.ConstantValue;
import net.bytebuddy.utility.JavaConstant;
import net.bytebuddy.utility.RandomString;
import net.bytebuddy.utility.nullability.AlwaysNull;
import net.bytebuddy.utility.nullability.MaybeNull;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import java.lang.reflect.Field;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import static net.bytebuddy.matcher.ElementMatchers.named;
/**
*
* Defines a method to access a given field by following the Java bean conventions for getters and setters:
*
*
* - Getter: A method named {@code getFoo()} will be instrumented to read and return the value of a field {@code foo}
* or another field if one was specified explicitly. If a property is of type {@link java.lang.Boolean} or
* {@code boolean}, the name {@code isFoo()} is also permitted.
* - Setter: A method named {@code setFoo(value)} will be instrumented to write the given argument {@code value}
* to a field {@code foo} or to another field if one was specified explicitly.
*
*
* Field accessors always implement a getter if a non-{@code void} value is returned from a method and attempt to define a setter
* otherwise. If a field accessor is not explicitly defined as a setter via {@link PropertyConfigurable}, an instrumented
* method must define exactly one parameter. Using the latter API, an explicit parameter index can be defined and a return
* value can be specified explicitly when {@code void} is not returned.
*
*/
@HashCodeAndEqualsPlugin.Enhance
public abstract class FieldAccessor implements Implementation {
/**
* The field's location.
*/
protected final FieldLocation fieldLocation;
/**
* The assigner to use.
*/
protected final Assigner assigner;
/**
* Indicates if dynamic type castings should be attempted for incompatible assignments.
*/
protected final Assigner.Typing typing;
/**
* Creates a new field accessor.
*
* @param fieldLocation The field's location.
* @param assigner The assigner to use.
* @param typing Indicates if dynamic type castings should be attempted for incompatible assignments.
*/
protected FieldAccessor(FieldLocation fieldLocation, Assigner assigner, Assigner.Typing typing) {
this.fieldLocation = fieldLocation;
this.assigner = assigner;
this.typing = typing;
}
/**
* Defines a field accessor where any access is targeted to a field named {@code name}.
*
* @param name The name of the field to be accessed.
* @return A field accessor for a field of a given name.
*/
public static OwnerTypeLocatable ofField(String name) {
return of(new FieldNameExtractor.ForFixedValue(name));
}
/**
* Defines a field accessor where any access is targeted to a field that matches the methods
* name with the Java specification for bean properties, i.e. a method {@code getFoo} or {@code setFoo(value)}
* will either read or write a field named {@code foo}.
*
* @return A field accessor that follows the Java naming conventions for bean properties.
*/
public static OwnerTypeLocatable ofBeanProperty() {
return of(FieldNameExtractor.ForBeanProperty.INSTANCE, FieldNameExtractor.ForBeanProperty.CAPITALIZED);
}
/**
* Defines a custom strategy for determining the field that is accessed by this field accessor.
*
* @param fieldNameExtractor The field name extractor to use.
* @return A field accessor using the given field name extractor.
*/
public static OwnerTypeLocatable of(FieldNameExtractor fieldNameExtractor) {
return of(Collections.singletonList(fieldNameExtractor));
}
/**
* Defines a custom strategy for determining the field that is accessed by this field accessor.
*
* @param fieldNameExtractor The field name extractors to use in their application order.
* @return A field accessor using the given field name extractor.
*/
public static OwnerTypeLocatable of(FieldNameExtractor... fieldNameExtractor) {
return of(Arrays.asList(fieldNameExtractor));
}
/**
* Defines a custom strategy for determining the field that is accessed by this field accessor.
*
* @param fieldNameExtractors The field name extractors to use in their application order.
* @return A field accessor using the given field name extractor.
*/
public static OwnerTypeLocatable of(List extends FieldNameExtractor> fieldNameExtractors) {
return new ForImplicitProperty(new FieldLocation.Relative(fieldNameExtractors));
}
/**
* Defines a field accessor where the specified field is accessed. The field must be within the hierarchy of the instrumented type.
*
* @param field The field being accessed.
* @return A field accessor for the given field.
*/
public static AssignerConfigurable of(Field field) {
return of(new FieldDescription.ForLoadedField(field));
}
/**
* Defines a field accessor where the specified field is accessed. The field must be within the hierarchy of the instrumented type.
*
* @param fieldDescription The field being accessed.
* @return A field accessor for the given field.
*/
public static AssignerConfigurable of(FieldDescription fieldDescription) {
return new ForImplicitProperty(new FieldLocation.Absolute(fieldDescription));
}
/**
* A field location represents an identified field description which depends on the instrumented type and method.
*/
protected interface FieldLocation {
/**
* Specifies a field locator factory to use.
*
* @param fieldLocatorFactory The field locator factory to use.
* @return An appropriate field location.
*/
FieldLocation with(FieldLocator.Factory fieldLocatorFactory);
/**
* A prepared field location.
*
* @param instrumentedType The instrumented type.
* @return A prepared field location.
*/
Prepared prepare(TypeDescription instrumentedType);
/**
* A prepared field location.
*/
interface Prepared {
/**
* Resolves the field description to use.
*
* @param instrumentedMethod The instrumented method.
* @return The resolved field description.
*/
FieldDescription resolve(MethodDescription instrumentedMethod);
}
/**
* An absolute field description representing a previously resolved field.
*/
@HashCodeAndEqualsPlugin.Enhance
class Absolute implements FieldLocation, Prepared {
/**
* The field description.
*/
private final FieldDescription fieldDescription;
/**
* Creates an absolute field location.
*
* @param fieldDescription The field description.
*/
protected Absolute(FieldDescription fieldDescription) {
this.fieldDescription = fieldDescription;
}
/**
* {@inheritDoc}
*/
public FieldLocation with(FieldLocator.Factory fieldLocatorFactory) {
throw new IllegalStateException("Cannot specify a field locator factory for an absolute field location");
}
/**
* {@inheritDoc}
*/
@SuppressFBWarnings(value = "NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE", justification = "Assuming declaring type for type member.")
public Prepared prepare(TypeDescription instrumentedType) {
if (!fieldDescription.isStatic() && !instrumentedType.isAssignableTo(fieldDescription.getDeclaringType().asErasure())) {
throw new IllegalStateException(fieldDescription + " is not declared by " + instrumentedType);
} else if (!fieldDescription.isAccessibleTo(instrumentedType)) {
throw new IllegalStateException("Cannot access " + fieldDescription + " from " + instrumentedType);
}
return this;
}
/**
* {@inheritDoc}
*/
public FieldDescription resolve(MethodDescription instrumentedMethod) {
return fieldDescription;
}
}
/**
* A relative field location where a field is located dynamically.
*/
@HashCodeAndEqualsPlugin.Enhance
class Relative implements FieldLocation {
/**
* The field name extractors to use in their application order.
*/
private final List extends FieldNameExtractor> fieldNameExtractors;
/**
* The field locator factory to use.
*/
private final FieldLocator.Factory fieldLocatorFactory;
/**
* Creates a new relative field location.
*
* @param fieldNameExtractors The field name extractors to use in their application order.
*/
protected Relative(List extends FieldNameExtractor> fieldNameExtractors) {
this(fieldNameExtractors, FieldLocator.ForClassHierarchy.Factory.INSTANCE);
}
/**
* Creates a new relative field location.
*
* @param fieldNameExtractors The field name extractors to use in their application order.
* @param fieldLocatorFactory The field locator factory to use.
*/
private Relative(List extends FieldNameExtractor> fieldNameExtractors, FieldLocator.Factory fieldLocatorFactory) {
this.fieldNameExtractors = fieldNameExtractors;
this.fieldLocatorFactory = fieldLocatorFactory;
}
/**
* {@inheritDoc}
*/
public FieldLocation with(FieldLocator.Factory fieldLocatorFactory) {
return new Relative(fieldNameExtractors, fieldLocatorFactory);
}
/**
* {@inheritDoc}
*/
public FieldLocation.Prepared prepare(TypeDescription instrumentedType) {
return new Prepared(fieldNameExtractors, fieldLocatorFactory.make(instrumentedType));
}
/**
* A prepared version of a field location.
*/
@HashCodeAndEqualsPlugin.Enhance
protected static class Prepared implements FieldLocation.Prepared {
/**
* The field name extractor to use in their application order.
*/
private final List extends FieldNameExtractor> fieldNameExtractors;
/**
* The field locator factory to use.
*/
private final FieldLocator fieldLocator;
/**
* Creates a new relative field location.
*
* @param fieldNameExtractors The field name extractors to use in their application order.
* @param fieldLocator The field locator to use.
*/
protected Prepared(List extends FieldNameExtractor> fieldNameExtractors, FieldLocator fieldLocator) {
this.fieldNameExtractors = fieldNameExtractors;
this.fieldLocator = fieldLocator;
}
/**
* {@inheritDoc}
*/
public FieldDescription resolve(MethodDescription instrumentedMethod) {
FieldLocator.Resolution resolution = FieldLocator.Resolution.Illegal.INSTANCE;
Iterator extends FieldNameExtractor> iterator = fieldNameExtractors.iterator();
while (iterator.hasNext() && !resolution.isResolved()) {
resolution = fieldLocator.locate(iterator.next().resolve(instrumentedMethod));
}
if (!resolution.isResolved()) {
throw new IllegalStateException("Cannot resolve field for " + instrumentedMethod + " using " + fieldLocator);
}
return resolution.getField();
}
}
}
}
/**
* A field name extractor is responsible for determining a field name to a method that is implemented
* to access this method.
*/
public interface FieldNameExtractor {
/**
* Extracts a field name to be accessed by a getter or setter method.
*
* @param methodDescription The method for which a field name is to be determined.
* @return The name of the field to be accessed by this method.
*/
String resolve(MethodDescription methodDescription);
/**
* A {@link net.bytebuddy.implementation.FieldAccessor.FieldNameExtractor} that determines a field name
* according to the rules of Java bean naming conventions.
*/
enum ForBeanProperty implements FieldNameExtractor {
/**
* A resolver that resolves a standard bean property name.
*/
INSTANCE {
@Override
protected char resolve(char character) {
return Character.toLowerCase(character);
}
},
/**
* A resolver that resolves a field name with a capital letter.
*/
CAPITALIZED {
@Override
protected char resolve(char character) {
return Character.toUpperCase(character);
}
};
/**
* {@inheritDoc}
*/
public String resolve(MethodDescription methodDescription) {
String name = methodDescription.getInternalName();
int crop;
if (name.startsWith("get") || name.startsWith("set")) {
crop = 3;
} else if (name.startsWith("is")) {
crop = 2;
} else {
throw new IllegalArgumentException(methodDescription + " does not follow Java bean naming conventions");
}
name = name.substring(crop);
if (name.length() == 0) {
throw new IllegalArgumentException(methodDescription + " does not specify a bean name");
}
return resolve(name.charAt(0)) + name.substring(1);
}
/**
* Resolves the first character of the bean property.
*
* @param character The first character.
* @return The resolved first character.
*/
protected abstract char resolve(char character);
}
/**
* A field name extractor that returns a fixed value.
*/
@HashCodeAndEqualsPlugin.Enhance
class ForFixedValue implements FieldNameExtractor {
/**
* The name to return.
*/
private final String name;
/**
* Creates a new field name extractor for a fixed value.
*
* @param name The name to return.
*/
protected ForFixedValue(String name) {
this.name = name;
}
/**
* {@inheritDoc}
*/
public String resolve(MethodDescription methodDescription) {
return name;
}
}
}
/**
* A field accessor that allows to define the access to be a field write of a given argument.
*/
public interface PropertyConfigurable extends Implementation {
/**
*
* Defines a setter of the specified parameter for the field being described.
*
*
* Note: If the instrumented method does not return {@code void}, a chained instrumentation must be supplied.
*
*
* @param index The index of the parameter for which to set the field's value.
* @return An instrumentation that sets the parameter's value to the described field.
*/
Composable setsArgumentAt(int index);
/**
*
* Defines a setter of the described field's default value, i.e. {@code null} or a primitive type's
* representation of {@code 0}.
*
*
* Note: If the instrumented method does not return {@code void}, a chained instrumentation must be supplied.
*
*
* @return An instrumentation that sets the field's default value.
*/
Composable setsDefaultValue();
/**
*
* Defines a setter of a given value for the described field. If the value is a constant value, it will be
* defined as a constant assignment, otherwise it is defined as a reference value that is stored in a static
* field of the instrumented type.
*
*
* Note: If the instrumented method does not return {@code void}, a chained instrumentation must be supplied.
*
*
* @param value The value to set.
* @return An instrumentation that sets the field's value as specified.
*/
Composable setsValue(Object value);
/**
*
* Defines a setter of a given class constant value for the described field.
*
*
* Note: If the instrumented method does not return {@code void}, a chained instrumentation must be supplied.
*
*
* @param typeDescription The type to set to the described field.
* @return An instrumentation that sets the field's value to the given class constant.
*/
Composable setsValue(TypeDescription typeDescription);
/**
*
* Defines a setter of a given constant value for the described field.
*
*
* Note: If the instrumented method does not return {@code void}, a chained instrumentation must be supplied.
*
*
* @param constant The constant to set as a value.
* @return An instrumentation that sets the field's value to the given constant.
*/
Composable setsValue(ConstantValue constant);
/**
*
* Defines a setter of a given constant value for the described field.
*
*
* Note: If the instrumented method does not return {@code void}, a chained instrumentation must be supplied.
*
*
* @param constant The constant to set as a value.
* @return An instrumentation that sets the field's value to the given constant.
*/
Composable setsValue(JavaConstant constant);
/**
*
* Defines a setter of a value that is represented by a stack manipulation.
*
*
* Note: If the instrumented method does not return {@code void}, a chained instrumentation must be supplied.
*
*
* @param stackManipulation A stack manipulation to load the field's value.
* @param type The field value's type.
* @return An instrumentation that sets the field's value to the given value.
*/
Composable setsValue(StackManipulation stackManipulation, Type type);
/**
*
* Defines a setter of a value that is represented by a stack manipulation.
*
*
* Note: If the instrumented method does not return {@code void}, a chained instrumentation must be supplied.
*
*
* @param stackManipulation A stack manipulation to load the field's value.
* @param typeDescription The field value's type.
* @return An instrumentation that sets the field's value to the given value.
*/
Composable setsValue(StackManipulation stackManipulation, TypeDescription.Generic typeDescription);
/**
*
* Defines a setter of a given value for the described field. The value is kept as a referenced that is stored
* in a static field of the instrumented type. The field name is chosen based on the value's hash code.
*
*
* Note: If the instrumented method does not return {@code void}, a chained instrumentation must be supplied.
*
*
* @param value The value to set.
* @return An instrumentation that sets the field's value as specified.
*/
Composable setsReference(Object value);
/**
*
* Defines a setter of a given value for the described field. The value is kept as a referenced that is stored
* in a static field of the instrumented type.
*
*
* Note: If the instrumented method does not return {@code void}, a chained instrumentation must be supplied.
*
*
* @param value The value to set.
* @param name The name of the field.
* @return An instrumentation that sets the field's value as specified.
*/
Composable setsReference(Object value, String name);
/**
*
* Defines a setter of a value that sets another field's value.
*
*
* Note: If the instrumented method does not return {@code void}, a chained instrumentation must be supplied.
*
*
* @param field The field that holds the value to be set.
* @return An instrumentation that sets the field's value to the specified field's value.
*/
Composable setsFieldValueOf(Field field);
/**
*
* Defines a setter of a value that sets another field's value.
*
*
* Note: If the instrumented method does not return {@code void}, a chained instrumentation must be supplied.
*
*
* @param fieldDescription The field that holds the value to be set.
* @return An instrumentation that sets the field's value to the specified field's value.
*/
Composable setsFieldValueOf(FieldDescription fieldDescription);
/**
*
* Defines a setter of a value that sets another field's value.
*
*
* Note: If the instrumented method does not return {@code void}, a chained instrumentation must be supplied.
*
*
* @param fieldName The name of the field that is specified by the instrumented type.
* @return An instrumentation that sets the field's value to the specified field's value.
*/
Composable setsFieldValueOf(String fieldName);
/**
*
* Defines a setter of a value that sets another field's value.
*
*
* Note: If the instrumented method does not return {@code void}, a chained instrumentation must be supplied.
*
*
* @param fieldNameExtractor A field name extractor for the field that is specified by the instrumented type.
* @return An instrumentation that sets the field's value to the specified field's value.
*/
Composable setsFieldValueOf(FieldNameExtractor fieldNameExtractor);
}
/**
* A field accessor that can be configured to use a given assigner and runtime type use configuration.
*/
public interface AssignerConfigurable extends PropertyConfigurable {
/**
* Returns a field accessor that is identical to this field accessor but uses the given assigner
* and runtime type use configuration.
*
* @param assigner The assigner to use.
* @param typing Indicates if dynamic type castings should be attempted for incompatible assignments.
* @return This field accessor with the given assigner and runtime type use configuration.
*/
PropertyConfigurable withAssigner(Assigner assigner, Assigner.Typing typing);
}
/**
* A field accessor that can be configured to locate a field in a specific manner.
*/
public interface OwnerTypeLocatable extends AssignerConfigurable {
/**
* Determines that a field should only be considered when it was defined in a given type.
*
* @param type The type to be considered.
* @return This field accessor which will only considered fields that are defined in the given type.
*/
AssignerConfigurable in(Class> type);
/**
* Determines that a field should only be considered when it was defined in a given type.
*
* @param typeDescription A description of the type to be considered.
* @return This field accessor which will only considered fields that are defined in the given type.
*/
AssignerConfigurable in(TypeDescription typeDescription);
/**
* Determines that a field should only be considered when it was identified by a field locator that is
* produced by the given factory.
*
* @param fieldLocatorFactory A factory that will produce a field locator that will be used to find locate
* a field to be accessed.
* @return This field accessor which will only considered fields that are defined in the given type.
*/
AssignerConfigurable in(FieldLocator.Factory fieldLocatorFactory);
}
/**
* A field accessor for an implicit property where a getter or setter property is inferred from the signature.
*/
protected static class ForImplicitProperty extends FieldAccessor implements OwnerTypeLocatable {
/**
* Creates a field accessor for an implicit property.
*
* @param fieldLocation The field's location.
*/
protected ForImplicitProperty(FieldLocation fieldLocation) {
this(fieldLocation, Assigner.DEFAULT, Assigner.Typing.STATIC);
}
/**
* Creates a field accessor for an implicit property.
*
* @param fieldLocation The field's location.
* @param assigner The assigner to use.
* @param typing The typing to use.
*/
private ForImplicitProperty(FieldLocation fieldLocation, Assigner assigner, Assigner.Typing typing) {
super(fieldLocation, assigner, typing);
}
/**
* {@inheritDoc}
*/
public InstrumentedType prepare(InstrumentedType instrumentedType) {
return instrumentedType;
}
/**
* {@inheritDoc}
*/
public ByteCodeAppender appender(Target implementationTarget) {
return new Appender(fieldLocation.prepare(implementationTarget.getInstrumentedType()));
}
/**
* {@inheritDoc}
*/
public Composable setsArgumentAt(int index) {
if (index < 0) {
throw new IllegalArgumentException("A parameter index cannot be negative: " + index);
}
return new ForSetter.OfParameterValue(fieldLocation,
assigner,
typing,
ForSetter.TerminationHandler.RETURNING,
index);
}
/**
* {@inheritDoc}
*/
public Composable setsDefaultValue() {
return new ForSetter.OfDefaultValue(fieldLocation, assigner, typing, ForSetter.TerminationHandler.RETURNING);
}
/**
* {@inheritDoc}
*/
public Composable setsValue(@MaybeNull Object value) {
if (value == null) {
return setsDefaultValue();
}
ConstantValue constant = ConstantValue.Simple.wrapOrNull(value);
return constant == null
? setsReference(value)
: setsValue(constant.toStackManipulation(), constant.getTypeDescription().asGenericType());
}
/**
* {@inheritDoc}
*/
public Composable setsValue(TypeDescription typeDescription) {
return setsValue(ClassConstant.of(typeDescription), Class.class);
}
/**
* {@inheritDoc}
*/
public Composable setsValue(ConstantValue constant) {
return setsValue(constant.toStackManipulation(), constant.getTypeDescription().asGenericType());
}
/**
* {@inheritDoc}
*/
public Composable setsValue(JavaConstant constant) {
return setsValue((ConstantValue) constant);
}
/**
* {@inheritDoc}
*/
public Composable setsValue(StackManipulation stackManipulation, Type type) {
return setsValue(stackManipulation, TypeDescription.Generic.Sort.describe(type));
}
/**
* {@inheritDoc}
*/
public Composable setsValue(StackManipulation stackManipulation, TypeDescription.Generic typeDescription) {
return new ForSetter.OfConstantValue(fieldLocation,
assigner,
typing,
ForSetter.TerminationHandler.RETURNING,
typeDescription,
stackManipulation);
}
/**
* {@inheritDoc}
*/
public Composable setsReference(Object value) {
return setsReference(value, ForSetter.OfReferenceValue.PREFIX + "$" + RandomString.hashOf(value));
}
/**
* {@inheritDoc}
*/
public Composable setsReference(Object value, String name) {
return new ForSetter.OfReferenceValue(fieldLocation,
assigner,
typing,
ForSetter.TerminationHandler.RETURNING,
value,
name);
}
/**
* {@inheritDoc}
*/
public Composable setsFieldValueOf(Field field) {
return setsFieldValueOf(new FieldDescription.ForLoadedField(field));
}
/**
* {@inheritDoc}
*/
public Composable setsFieldValueOf(FieldDescription fieldDescription) {
return new ForSetter.OfFieldValue(fieldLocation,
assigner,
typing,
ForSetter.TerminationHandler.RETURNING,
new FieldLocation.Absolute(fieldDescription));
}
/**
* {@inheritDoc}
*/
public Composable setsFieldValueOf(String fieldName) {
return setsFieldValueOf(new FieldNameExtractor.ForFixedValue(fieldName));
}
/**
* {@inheritDoc}
*/
public Composable setsFieldValueOf(FieldNameExtractor fieldNameExtractor) {
return new ForSetter.OfFieldValue(fieldLocation,
assigner,
typing,
ForSetter.TerminationHandler.RETURNING,
new FieldLocation.Relative(Collections.singletonList(fieldNameExtractor)));
}
/**
* {@inheritDoc}
*/
public PropertyConfigurable withAssigner(Assigner assigner, Assigner.Typing typing) {
return new ForImplicitProperty(fieldLocation, assigner, typing);
}
/**
* {@inheritDoc}
*/
public AssignerConfigurable in(Class> type) {
return in(TypeDescription.ForLoadedType.of(type));
}
/**
* {@inheritDoc}
*/
public AssignerConfigurable in(TypeDescription typeDescription) {
return in(new FieldLocator.ForExactType.Factory(typeDescription));
}
/**
* {@inheritDoc}
*/
public AssignerConfigurable in(FieldLocator.Factory fieldLocatorFactory) {
return new ForImplicitProperty(fieldLocation.with(fieldLocatorFactory), assigner, typing);
}
/**
* An byte code appender for an field accessor implementation.
*/
@HashCodeAndEqualsPlugin.Enhance(includeSyntheticFields = true)
protected class Appender implements ByteCodeAppender {
/**
* The field's location.
*/
private final FieldLocation.Prepared fieldLocation;
/**
* Creates a new byte code appender for a field accessor implementation.
*
* @param fieldLocation The field's location.
*/
protected Appender(FieldLocation.Prepared fieldLocation) {
this.fieldLocation = fieldLocation;
}
/**
* {@inheritDoc}
*/
public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext, MethodDescription instrumentedMethod) {
if (!instrumentedMethod.isMethod()) {
throw new IllegalArgumentException(instrumentedMethod + " does not describe a field getter or setter");
}
FieldDescription fieldDescription = fieldLocation.resolve(instrumentedMethod);
if (!fieldDescription.isStatic() && instrumentedMethod.isStatic()) {
throw new IllegalStateException("Cannot set instance field " + fieldDescription + " from " + instrumentedMethod);
}
StackManipulation implementation, initialization = fieldDescription.isStatic()
? StackManipulation.Trivial.INSTANCE
: MethodVariableAccess.loadThis();
if (!instrumentedMethod.getReturnType().represents(void.class)) {
implementation = new StackManipulation.Compound(
initialization,
FieldAccess.forField(fieldDescription).read(),
assigner.assign(fieldDescription.getType(), instrumentedMethod.getReturnType(), typing),
MethodReturn.of(instrumentedMethod.getReturnType())
);
} else if (instrumentedMethod.getReturnType().represents(void.class) && instrumentedMethod.getParameters().size() == 1) {
if (fieldDescription.isFinal() && instrumentedMethod.isMethod()) {
throw new IllegalStateException("Cannot set final field " + fieldDescription + " from " + instrumentedMethod);
}
implementation = new StackManipulation.Compound(
initialization,
MethodVariableAccess.load(instrumentedMethod.getParameters().get(0)),
assigner.assign(instrumentedMethod.getParameters().get(0).getType(), fieldDescription.getType(), typing),
FieldAccess.forField(fieldDescription).write(),
MethodReturn.VOID
);
} else {
throw new IllegalArgumentException("Method " + instrumentedMethod + " is no bean accessor");
}
if (!implementation.isValid()) {
throw new IllegalStateException("Cannot set or get value of " + instrumentedMethod + " using " + fieldDescription);
}
return new Size(implementation.apply(methodVisitor, implementationContext).getMaximalSize(), instrumentedMethod.getStackSize());
}
}
}
/**
* A field accessor for a field setter.
*
* @param The type of the value that is initialized per instrumented type.
*/
@HashCodeAndEqualsPlugin.Enhance
protected abstract static class ForSetter extends FieldAccessor implements Implementation.Composable {
/**
* The termination handler to apply.
*/
private final TerminationHandler terminationHandler;
/**
* Creates a new field accessor for a setter instrumentation.
*
* @param fieldLocation The field's location.
* @param assigner The assigner to use.
* @param typing Indicates if dynamic type castings should be attempted for incompatible assignments.
* @param terminationHandler The termination handler to apply.
*/
protected ForSetter(FieldLocation fieldLocation, Assigner assigner, Assigner.Typing typing, TerminationHandler terminationHandler) {
super(fieldLocation, assigner, typing);
this.terminationHandler = terminationHandler;
}
/**
* {@inheritDoc}
*/
public ByteCodeAppender appender(Target implementationTarget) {
return new Appender(implementationTarget.getInstrumentedType(),
initialize(implementationTarget.getInstrumentedType()),
fieldLocation.prepare(implementationTarget.getInstrumentedType()));
}
/**
* Initializes a value to be used during method instrumentation.
*
* @param instrumentedType The instrumented type.
* @return The initialized value.
*/
@MaybeNull
protected abstract T initialize(TypeDescription instrumentedType);
/**
* Resolves the stack manipulation to load the value being set.
*
* @param initialized The method that was initialized for the instrumented type.
* @param fieldDescription The field to set the value for.
* @param instrumentedType The instrumented type.
* @param instrumentedMethod The instrumented method.
* @return The stack manipulation to apply.
*/
protected abstract StackManipulation resolve(@MaybeNull T initialized,
FieldDescription fieldDescription,
TypeDescription instrumentedType,
MethodDescription instrumentedMethod);
/**
* A termination handler is responsible for handling a field accessor's return.
*/
protected enum TerminationHandler {
/**
* Returns {@code void} or throws an exception if this is not the return type of the instrumented method.
*/
RETURNING {
@Override
protected StackManipulation resolve(MethodDescription instrumentedMethod) {
if (!instrumentedMethod.getReturnType().represents(void.class)) {
throw new IllegalStateException("Cannot implement setter with return value for " + instrumentedMethod);
}
return MethodReturn.VOID;
}
},
/**
* Does not return from the method at all.
*/
NON_OPERATIONAL {
@Override
protected StackManipulation resolve(MethodDescription instrumentedMethod) {
return StackManipulation.Trivial.INSTANCE;
}
};
/**
* Resolves the return instruction.
*
* @param instrumentedMethod The instrumented method.
* @return An appropriate stack manipulation.
*/
protected abstract StackManipulation resolve(MethodDescription instrumentedMethod);
}
/**
* A setter instrumentation for a parameter value.
*/
@HashCodeAndEqualsPlugin.Enhance
protected static class OfParameterValue extends ForSetter {
/**
* The parameter's index.
*/
private final int index;
/**
* Creates a new setter instrumentation for a parameter value.
*
* @param fieldLocation The field's location.
* @param assigner The assigner to use.
* @param typing Indicates if dynamic type castings should be attempted for incompatible assignments.
* @param terminationHandler The termination handler to apply.
* @param index The parameter's index.
*/
protected OfParameterValue(FieldLocation fieldLocation,
Assigner assigner,
Assigner.Typing typing,
TerminationHandler terminationHandler,
int index) {
super(fieldLocation, assigner, typing, terminationHandler);
this.index = index;
}
/**
* {@inheritDoc}
*/
public InstrumentedType prepare(InstrumentedType instrumentedType) {
return instrumentedType;
}
/**
* {@inheritDoc}
*/
@AlwaysNull
protected Void initialize(TypeDescription instrumentedType) {
return null;
}
/**
* {@inheritDoc}
*/
protected StackManipulation resolve(@MaybeNull Void unused,
FieldDescription fieldDescription,
TypeDescription instrumentedType,
MethodDescription instrumentedMethod) {
if (instrumentedMethod.getParameters().size() <= index) {
throw new IllegalStateException(instrumentedMethod + " does not define a parameter with index " + index);
} else {
return new StackManipulation.Compound(
MethodVariableAccess.load(instrumentedMethod.getParameters().get(index)),
assigner.assign(instrumentedMethod.getParameters().get(index).getType(), fieldDescription.getType(), typing)
);
}
}
/**
* {@inheritDoc}
*/
public Implementation andThen(Implementation implementation) {
return new Compound(new OfParameterValue(fieldLocation,
assigner,
typing,
TerminationHandler.NON_OPERATIONAL,
index), implementation);
}
/**
* {@inheritDoc}
*/
public Composable andThen(Composable implementation) {
return new Compound.Composable(new OfParameterValue(fieldLocation,
assigner,
typing,
TerminationHandler.NON_OPERATIONAL,
index), implementation);
}
}
/**
* A setter instrumentation that sets a {@code null} or a primitive type's default value.
*/
protected static class OfDefaultValue extends ForSetter {
/**
* Creates an intrumentation that sets a field's default value.
*
* @param fieldLocation The field's location.
* @param assigner The assigner to use.
* @param typing Indicates if dynamic type castings should be attempted for incompatible assignments.
* @param terminationHandler The termination handler to apply.
*/
protected OfDefaultValue(FieldLocation fieldLocation, Assigner assigner, Assigner.Typing typing, TerminationHandler terminationHandler) {
super(fieldLocation, assigner, typing, terminationHandler);
}
/**
* {@inheritDoc}
*/
public InstrumentedType prepare(InstrumentedType instrumentedType) {
return instrumentedType;
}
/**
* {@inheritDoc}
*/
@AlwaysNull
protected Void initialize(TypeDescription instrumentedType) {
return null;
}
/**
* {@inheritDoc}
*/
protected StackManipulation resolve(@MaybeNull Void initialized,
FieldDescription fieldDescription,
TypeDescription instrumentedType,
MethodDescription instrumentedMethod) {
return DefaultValue.of(fieldDescription.getType());
}
/**
* {@inheritDoc}
*/
public Implementation andThen(Implementation implementation) {
return new Compound(new OfDefaultValue(fieldLocation,
assigner,
typing,
TerminationHandler.NON_OPERATIONAL), implementation);
}
/**
* {@inheritDoc}
*/
public Composable andThen(Composable implementation) {
return new Compound.Composable(new OfDefaultValue(fieldLocation,
assigner,
typing,
TerminationHandler.NON_OPERATIONAL), implementation);
}
}
/**
* An instrumentation that sets a constant value to a field.
*/
@HashCodeAndEqualsPlugin.Enhance
protected static class OfConstantValue extends ForSetter {
/**
* The value's type.
*/
private final TypeDescription.Generic typeDescription;
/**
* A stack manipulation to load the constant value.
*/
private final StackManipulation stackManipulation;
/**
* Creates a setter instrumentation for setting a constant value.
*
* @param fieldLocation The field's location.
* @param assigner The assigner to use.
* @param typing Indicates if dynamic type castings should be attempted for incompatible assignments.
* @param terminationHandler The termination handler to apply.
* @param typeDescription The value's type.
* @param stackManipulation A stack manipulation to load the constant value.
*/
protected OfConstantValue(FieldLocation fieldLocation,
Assigner assigner,
Assigner.Typing typing,
TerminationHandler terminationHandler,
TypeDescription.Generic typeDescription,
StackManipulation stackManipulation) {
super(fieldLocation, assigner, typing, terminationHandler);
this.typeDescription = typeDescription;
this.stackManipulation = stackManipulation;
}
/**
* {@inheritDoc}
*/
public InstrumentedType prepare(InstrumentedType instrumentedType) {
return instrumentedType;
}
/**
* {@inheritDoc}
*/
@AlwaysNull
protected Void initialize(TypeDescription instrumentedType) {
return null;
}
/**
* {@inheritDoc}
*/
protected StackManipulation resolve(@MaybeNull Void unused,
FieldDescription fieldDescription,
TypeDescription instrumentedType,
MethodDescription instrumentedMethod) {
return new StackManipulation.Compound(stackManipulation, assigner.assign(typeDescription, fieldDescription.getType(), typing));
}
/**
* {@inheritDoc}
*/
public Implementation andThen(Implementation implementation) {
return new Compound(new OfConstantValue(fieldLocation,
assigner,
typing,
TerminationHandler.NON_OPERATIONAL,
typeDescription,
stackManipulation), implementation);
}
/**
* {@inheritDoc}
*/
public Composable andThen(Composable implementation) {
return new Compound.Composable(new OfConstantValue(fieldLocation,
assigner,
typing,
TerminationHandler.NON_OPERATIONAL,
typeDescription,
stackManipulation), implementation);
}
}
/**
* An instrumentation that sets a field to a reference value that is stored in a static field of the instrumented type.
*/
@HashCodeAndEqualsPlugin.Enhance
protected static class OfReferenceValue extends ForSetter {
/**
* The prefix used for implicitly named cached fields.
*/
protected static final String PREFIX = "fixedFieldValue";
/**
* The value to store.
*/
private final Object value;
/**
* The name of the field to store the reference in.
*/
private final String name;
/**
* Creates a setter instrumentation for setting a value stored in a static field of the instrumented type.
*
* @param fieldLocation The field's location.
* @param assigner The assigner to use.
* @param typing Indicates if dynamic type castings should be attempted for incompatible assignments.
* @param terminationHandler The termination handler to apply.
* @param value The value to store.
* @param name The name of the field to store the reference in.
*/
protected OfReferenceValue(FieldLocation fieldLocation,
Assigner assigner,
Assigner.Typing typing,
TerminationHandler terminationHandler,
Object value,
String name) {
super(fieldLocation, assigner, typing, terminationHandler);
this.value = value;
this.name = name;
}
/**
* {@inheritDoc}
*/
public InstrumentedType prepare(InstrumentedType instrumentedType) {
return instrumentedType.withAuxiliaryField(new FieldDescription.Token(name,
Opcodes.ACC_SYNTHETIC | Opcodes.ACC_STATIC | Opcodes.ACC_PUBLIC,
TypeDescription.ForLoadedType.of(value.getClass()).asGenericType()), value);
}
/**
* {@inheritDoc}
*/
protected FieldDescription.InDefinedShape initialize(TypeDescription instrumentedType) {
return instrumentedType.getDeclaredFields().filter(named(name)).getOnly();
}
/**
* {@inheritDoc}
*/
@SuppressFBWarnings(value = "NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE", justification = "Expects its own initialized value as argument")
protected StackManipulation resolve(@MaybeNull FieldDescription.InDefinedShape target,
FieldDescription fieldDescription,
TypeDescription instrumentedType,
MethodDescription instrumentedMethod) {
if (fieldDescription.isFinal() && instrumentedMethod.isMethod()) {
throw new IllegalArgumentException("Cannot set final field " + fieldDescription + " from " + instrumentedMethod);
}
return new StackManipulation.Compound(
FieldAccess.forField(target).read(),
assigner.assign(TypeDescription.ForLoadedType.of(value.getClass()).asGenericType(), fieldDescription.getType(), typing)
);
}
/**
* {@inheritDoc}
*/
public Implementation andThen(Implementation implementation) {
return new Compound(new OfReferenceValue(fieldLocation,
assigner,
typing,
TerminationHandler.NON_OPERATIONAL,
value,
name), implementation);
}
/**
* {@inheritDoc}
*/
public Composable andThen(Composable implementation) {
return new Compound.Composable(new OfReferenceValue(fieldLocation,
assigner,
typing,
TerminationHandler.NON_OPERATIONAL,
value,
name), implementation);
}
}
/**
* A setter that reads a value of another field and sets this value.
*/
@HashCodeAndEqualsPlugin.Enhance
protected static class OfFieldValue extends ForSetter {
/**
* The target field locator.
*/
private final FieldLocation target;
/**
* Creates a setter that sets another field value.
*
* @param fieldLocation The field's location.
* @param assigner The assigner to use.
* @param typing Indicates if dynamic type castings should be attempted for incompatible assignments.
* @param terminationHandler The termination handler to apply.
* @param target The target field locator.
*/
protected OfFieldValue(FieldLocation fieldLocation,
Assigner assigner,
Assigner.Typing typing,
TerminationHandler terminationHandler,
FieldLocation target) {
super(fieldLocation, assigner, typing, terminationHandler);
this.target = target;
}
/**
* {@inheritDoc}
*/
public InstrumentedType prepare(InstrumentedType instrumentedType) {
return instrumentedType;
}
/**
* {@inheritDoc}
*/
protected FieldLocation.Prepared initialize(TypeDescription instrumentedType) {
return target.prepare(instrumentedType);
}
/**
* {@inheritDoc}
*/
@SuppressFBWarnings(value = "NP_PARAMETER_MUST_BE_NONNULL_BUT_MARKED_AS_NULLABLE", justification = "Expects its own initialized value as argument")
protected StackManipulation resolve(@MaybeNull FieldLocation.Prepared target,
FieldDescription fieldDescription,
TypeDescription instrumentedType,
MethodDescription instrumentedMethod) {
FieldDescription resolved = target.resolve(instrumentedMethod);
if (!resolved.isStatic() && instrumentedMethod.isStatic()) {
throw new IllegalStateException("Cannot set instance field " + fieldDescription + " from " + instrumentedMethod);
}
return new StackManipulation.Compound(
resolved.isStatic()
? StackManipulation.Trivial.INSTANCE
: MethodVariableAccess.loadThis(),
FieldAccess.forField(resolved).read(),
assigner.assign(resolved.getType(), fieldDescription.getType(), typing)
);
}
/**
* {@inheritDoc}
*/
public Implementation andThen(Implementation implementation) {
return new Compound(new OfFieldValue(fieldLocation,
assigner,
typing,
TerminationHandler.NON_OPERATIONAL,
target), implementation);
}
/**
* {@inheritDoc}
*/
public Composable andThen(Composable implementation) {
return new Compound.Composable(new OfFieldValue(fieldLocation,
assigner,
typing,
TerminationHandler.NON_OPERATIONAL,
target), implementation);
}
}
/**
* An appender to implement a field setter.
*/
@HashCodeAndEqualsPlugin.Enhance(includeSyntheticFields = true)
protected class Appender implements ByteCodeAppender {
/**
* The instrumented type.
*/
private final TypeDescription instrumentedType;
/**
* The initialized value which might be {@code null}.
*/
@MaybeNull
@HashCodeAndEqualsPlugin.ValueHandling(HashCodeAndEqualsPlugin.ValueHandling.Sort.REVERSE_NULLABILITY)
private final T initialized;
/**
* The set field's prepared location.
*/
private final FieldLocation.Prepared fieldLocation;
/**
* Creates a new appender for a field setter.
*
* @param instrumentedType The instrumented type.
* @param initialized The initialized value which might be {@code null}.
* @param fieldLocation The set field's prepared location.
*/
protected Appender(TypeDescription instrumentedType, @MaybeNull T initialized, FieldLocation.Prepared fieldLocation) {
this.instrumentedType = instrumentedType;
this.initialized = initialized;
this.fieldLocation = fieldLocation;
}
/**
* {@inheritDoc}
*/
public Size apply(MethodVisitor methodVisitor, Context implementationContext, MethodDescription instrumentedMethod) {
FieldDescription fieldDescription = fieldLocation.resolve(instrumentedMethod);
if (!fieldDescription.isStatic() && instrumentedMethod.isStatic()) {
throw new IllegalStateException("Cannot set instance field " + fieldDescription + " from " + instrumentedMethod);
} else if (fieldDescription.isFinal() && instrumentedMethod.isMethod()) {
throw new IllegalStateException("Cannot set final field " + fieldDescription + " from " + instrumentedMethod);
}
StackManipulation stackManipulation = resolve(initialized, fieldDescription, instrumentedType, instrumentedMethod);
if (!stackManipulation.isValid()) {
throw new IllegalStateException("Set value cannot be assigned to " + fieldDescription);
}
return new Size(new StackManipulation.Compound(
instrumentedMethod.isStatic()
? StackManipulation.Trivial.INSTANCE
: MethodVariableAccess.loadThis(),
stackManipulation,
FieldAccess.forField(fieldDescription).write(),
terminationHandler.resolve(instrumentedMethod)
).apply(methodVisitor, implementationContext).getMaximalSize(), instrumentedMethod.getStackSize());
}
}
}
}