/*
* 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.bind.annotation;
import net.bytebuddy.
ByteBuddy;
import net.bytebuddy.
ClassFileVersion;
import net.bytebuddy.build.
HashCodeAndEqualsPlugin;
import net.bytebuddy.description.annotation.
AnnotationDescription;
import net.bytebuddy.description.field.
FieldDescription;
import net.bytebuddy.description.method.
MethodDescription;
import net.bytebuddy.description.method.
MethodList;
import net.bytebuddy.description.method.
ParameterDescription;
import net.bytebuddy.description.type.
TypeDescription;
import net.bytebuddy.dynamic.
DynamicType;
import net.bytebuddy.dynamic.scaffold.
InstrumentedType;
import net.bytebuddy.dynamic.scaffold.
TypeValidation;
import net.bytebuddy.dynamic.scaffold.subclass.
ConstructorStrategy;
import net.bytebuddy.implementation.
ExceptionMethod;
import net.bytebuddy.implementation.
Implementation;
import net.bytebuddy.implementation.
MethodAccessorFactory;
import net.bytebuddy.implementation.auxiliary.
AuxiliaryType;
import net.bytebuddy.implementation.bind.
MethodDelegationBinder;
import net.bytebuddy.implementation.bytecode.
ByteCodeAppender;
import net.bytebuddy.implementation.bytecode.
Duplication;
import net.bytebuddy.implementation.bytecode.
StackManipulation;
import net.bytebuddy.implementation.bytecode.
TypeCreation;
import net.bytebuddy.implementation.bytecode.assign.
Assigner;
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.jar.asm.
MethodVisitor;
import net.bytebuddy.jar.asm.
Opcodes;
import java.io.
Serializable;
import java.lang.annotation.*;
import java.util.
Collections;
import static net.bytebuddy.matcher.
ElementMatchers.*;
/**
* Using this annotation it is possible to access fields by getter and setter types. Before this annotation can be
* used, it needs to be installed with two types. The getter type must be defined in a single-method interface
* with a single method that returns an {@link java.lang.Object} type and takes no arguments. The setter interface
* must similarly return {@code void} and take a single {@link java.lang.Object} argument. After installing these
* interfaces with the {@link FieldProxy.Binder}, this
* binder needs to be registered with a {@link net.bytebuddy.implementation.MethodDelegation} before it can be used.
*
* @see net.bytebuddy.implementation.MethodDelegation
* @see net.bytebuddy.implementation.bind.annotation.TargetMethodAnnotationDrivenBinder
*/
@
Documented
@
Retention(
RetentionPolicy.
RUNTIME)
@
Target(
ElementType.
PARAMETER)
public @interface
FieldProxy {
/**
* Determines if the proxy should be serializable.
*
* @return {@code true} if the proxy should be serializable.
*/
boolean serializableProxy() default false;
/**
* Determines the name of the field that is to be accessed. If this property is not set, a field name is inferred
* by the intercepted method after the Java beans naming conventions.
*
* @return The name of the field to be accessed.
*/
String value() default
TargetMethodAnnotationDrivenBinder.
ParameterBinder.
ForFieldBinding.
BEAN_PROPERTY;
/**
* Determines which type defines the field that is to be accessed. If this property is not set, the most field
* that is defined highest in the type hierarchy is accessed.
*
* @return The type that defines the accessed field.
*/
Class<?> declaringType() default void.class;
/**
* A binder for the {@link FieldProxy} annotation.
*/
@
HashCodeAndEqualsPlugin.
Enhance
class
Binder extends
TargetMethodAnnotationDrivenBinder.
ParameterBinder.
ForFieldBinding<
FieldProxy> {
/**
* A reference to the method that declares the field annotation's defining type property.
*/
private static final
MethodDescription.
InDefinedShape DECLARING_TYPE;
/**
* A reference to the method that declares the field annotation's field name property.
*/
private static final
MethodDescription.
InDefinedShape FIELD_NAME;
/**
* A reference to the method that declares the field annotation's serializable proxy property.
*/
private static final
MethodDescription.
InDefinedShape SERIALIZABLE_PROXY;
/*
* Fetches a reference to all annotation properties.
*/
static {
MethodList<
MethodDescription.
InDefinedShape>
methodList =
TypeDescription.
ForLoadedType.
of(
FieldProxy.class).
getDeclaredMethods();
DECLARING_TYPE =
methodList.
filter(
named("declaringType")).
getOnly();
FIELD_NAME =
methodList.
filter(
named("value")).
getOnly();
SERIALIZABLE_PROXY =
methodList.
filter(
named("serializableProxy")).
getOnly();
}
/**
* Creates a binder by installing a single proxy type where annotating a parameter with {@link FieldProxy} allows
* getting and setting values for a given field.
*
* @param type A type which declares exactly one abstract getter and an abstract setter for the {@link Object}
* type. The type is allowed to be generic.
* @return A binder for the {@link FieldProxy} annotation.
*/
public static
TargetMethodAnnotationDrivenBinder.
ParameterBinder<
FieldProxy>
install(
Class<?>
type) {
return
install(
TypeDescription.
ForLoadedType.
of(
type));
}
/**
* Creates a binder by installing a single proxy type where annotating a parameter with {@link FieldProxy} allows
* getting and setting values for a given field.
*
* @param typeDescription A type which declares exactly one abstract getter and an abstract setter for the {@link Object}
* type. The type is allowed to be generic.
* @return A binder for the {@link FieldProxy} annotation.
*/
public static
TargetMethodAnnotationDrivenBinder.
ParameterBinder<
FieldProxy>
install(
TypeDescription typeDescription) {
if (!
typeDescription.
isInterface()) {
throw new
IllegalArgumentException(
typeDescription + " is not an interface");
} else if (!
typeDescription.
getInterfaces().
isEmpty()) {
throw new
IllegalArgumentException(
typeDescription + " must not extend other interfaces");
} else if (!
typeDescription.
isPublic()) {
throw new
IllegalArgumentException(
typeDescription + " is not public");
}
MethodList<
MethodDescription.
InDefinedShape>
methodCandidates =
typeDescription.
getDeclaredMethods().
filter(
isAbstract());
if (
methodCandidates.
size() != 2) {
throw new
IllegalArgumentException(
typeDescription + " does not declare exactly two non-abstract methods");
}
MethodList<
MethodDescription.
InDefinedShape>
getterCandidates =
methodCandidates.
filter(
isGetter(
Object.class));
if (
getterCandidates.
size() != 1) {
throw new
IllegalArgumentException(
typeDescription + " does not declare a getter with an Object type");
}
MethodList<
MethodDescription.
InDefinedShape>
setterCandidates =
methodCandidates.
filter(
isSetter(
Object.class));
if (
setterCandidates.
size() != 1) {
throw new
IllegalArgumentException(
typeDescription + " does not declare a setter with an Object type");
}
return new
Binder(
typeDescription,
getterCandidates.
getOnly(),
setterCandidates.
getOnly());
}
/**
* Creates a binder by installing two proxy types which are implemented by this binder if a field getter
* or a field setter is requested by using the
* {@link FieldProxy} annotation.
*
* @param getterType The type which should be used for getter proxies. The type must
* represent an interface which defines a single method which returns an
* {@link java.lang.Object} return type and does not take any arguments. The use of generics
* is permitted.
* @param setterType The type which should be uses for setter proxies. The type must
* represent an interface which defines a single method which returns {@code void}
* and takes a single {@link java.lang.Object}-typed argument. The use of generics
* is permitted.
* @return A binder for the {@link FieldProxy} annotation.
*/
public static
TargetMethodAnnotationDrivenBinder.
ParameterBinder<
FieldProxy>
install(
Class<?>
getterType,
Class<?>
setterType) {
return
install(
TypeDescription.
ForLoadedType.
of(
getterType),
TypeDescription.
ForLoadedType.
of(
setterType));
}
/**
* Creates a binder by installing two proxy types which are implemented by this binder if a field getter
* or a field setter is requested by using the
* {@link FieldProxy} annotation.
*
* @param getterType The type which should be used for getter proxies. The type must
* represent an interface which defines a single method which returns an
* {@link java.lang.Object} return type and does not take any arguments. The use of generics
* is permitted.
* @param setterType The type which should be uses for setter proxies. The type must
* represent an interface which defines a single method which returns {@code void}
* and takes a single {@link java.lang.Object}-typed argument. The use of generics
* is permitted.
* @return A binder for the {@link FieldProxy} annotation.
*/
public static
TargetMethodAnnotationDrivenBinder.
ParameterBinder<
FieldProxy>
install(
TypeDescription getterType,
TypeDescription setterType) {
MethodDescription.
InDefinedShape getterMethod =
onlyMethod(
getterType);
if (!
getterMethod.
getReturnType().
asErasure().
represents(
Object.class)) {
throw new
IllegalArgumentException(
getterMethod + " must take a single Object-typed parameter");
} else if (
getterMethod.
getParameters().
size() != 0) {
throw new
IllegalArgumentException(
getterMethod + " must not declare parameters");
}
MethodDescription.
InDefinedShape setterMethod =
onlyMethod(
setterType);
if (!
setterMethod.
getReturnType().
asErasure().
represents(void.class)) {
throw new
IllegalArgumentException(
setterMethod + " must return void");
} else if (
setterMethod.
getParameters().
size() != 1 || !
setterMethod.
getParameters().
get(0).
getType().
asErasure().
represents(
Object.class)) {
throw new
IllegalArgumentException(
setterMethod + " must declare a single Object-typed parameters");
}
return new
Binder(
getterMethod,
setterMethod);
}
/**
* Extracts the only method from a given type description which is validated for the required properties for
* using the type as a proxy base type.
*
* @param typeDescription The type description to evaluate.
* @return The only method which was found to be compatible to the proxy requirements.
*/
private static
MethodDescription.
InDefinedShape onlyMethod(
TypeDescription typeDescription) {
if (!
typeDescription.
isInterface()) {
throw new
IllegalArgumentException(
typeDescription + " is not an interface");
} else if (!
typeDescription.
getInterfaces().
isEmpty()) {
throw new
IllegalArgumentException(
typeDescription + " must not extend other interfaces");
} else if (!
typeDescription.
isPublic()) {
throw new
IllegalArgumentException(
typeDescription + " is not public");
}
MethodList<
MethodDescription.
InDefinedShape>
methodCandidates =
typeDescription.
getDeclaredMethods().
filter(
isAbstract());
if (
methodCandidates.
size() != 1) {
throw new
IllegalArgumentException(
typeDescription + " must declare exactly one abstract method");
}
return
methodCandidates.
getOnly();
}
/**
* The field resolver factory to apply by this binder.
*/
private final
FieldResolver.
Factory fieldResolverFactory;
/**
* Creates a new binder for a {@link FieldProxy} in simplex mode.
*
* @param getterMethod The getter method.
* @param setterMethod The setter method.
*/
protected
Binder(
MethodDescription.
InDefinedShape getterMethod,
MethodDescription.
InDefinedShape setterMethod) {
this(new
FieldResolver.
Factory.
Simplex(
getterMethod,
setterMethod));
}
/**
* Creates a new binder for a {@link FieldProxy} in duplex mode.
*
* @param proxyType The proxy type.
* @param getterMethod The getter method.
* @param setterMethod The setter method.
*/
protected
Binder(
TypeDescription proxyType,
MethodDescription.
InDefinedShape getterMethod,
MethodDescription.
InDefinedShape setterMethod) {
this(new
FieldResolver.
Factory.
Duplex(
proxyType,
getterMethod,
setterMethod));
}
/**
* Creates a new binder for a {@link FieldProxy}.
*
* @param fieldResolverFactory The field resolver factory to apply by this binder.
*/
protected
Binder(
FieldResolver.
Factory fieldResolverFactory) {
this.
fieldResolverFactory =
fieldResolverFactory;
}
/**
* {@inheritDoc}
*/
public
Class<
FieldProxy>
getHandledType() {
return
FieldProxy.class;
}
@
Override
protected
String fieldName(
AnnotationDescription.
Loadable<
FieldProxy>
annotation) {
return
annotation.
getValue(
FIELD_NAME).
resolve(
String.class);
}
@
Override
protected
TypeDescription declaringType(
AnnotationDescription.
Loadable<
FieldProxy>
annotation) {
return
annotation.
getValue(
DECLARING_TYPE).
resolve(
TypeDescription.class);
}
@
Override
protected
MethodDelegationBinder.
ParameterBinding<?>
bind(
FieldDescription fieldDescription,
AnnotationDescription.
Loadable<
FieldProxy>
annotation,
MethodDescription source,
ParameterDescription target,
Implementation.
Target implementationTarget,
Assigner assigner) {
FieldResolver fieldResolver =
fieldResolverFactory.
resolve(
target.
getType().
asErasure(),
fieldDescription);
if (
fieldResolver.
isResolved()) {
return new
MethodDelegationBinder.
ParameterBinding.
Anonymous(new
AccessorProxy(
fieldDescription,
implementationTarget.
getInstrumentedType(),
fieldResolver,
assigner,
annotation.
getValue(
SERIALIZABLE_PROXY).
resolve(
Boolean.class)));
} else {
return
MethodDelegationBinder.
ParameterBinding.
Illegal.
INSTANCE;
}
}
/**
* A resolver for creating an instrumentation for a field access.
*/
protected interface
FieldResolver {
/**
* Returns {@code true} if the field access can be established.
*
* @return {@code true} if the field access can be established.
*/
boolean
isResolved();
/**
* Returns the type of the field access proxy.
*
* @return The type of the field access proxy.
*/
TypeDescription getProxyType();
/**
* Applies this field resolver to a dynamic type.
*
* @param builder The dynamic type builder to use.
* @param fieldDescription The accessed field.
* @param assigner The assigner to use.
* @param methodAccessorFactory The method accessor factory to use.
* @return The builder for creating the field accessor proxy type.
*/
DynamicType.
Builder<?>
apply(
DynamicType.
Builder<?>
builder,
FieldDescription fieldDescription,
Assigner assigner,
MethodAccessorFactory methodAccessorFactory);
/**
* A factory for creating a field resolver.
*/
interface
Factory {
/**
* Creates a field resolver.
*
* @param parameterType The type of the annotated parameter.
* @param fieldDescription The field being proxied.
* @return An appropriate field resolver.
*/
FieldResolver resolve(
TypeDescription parameterType,
FieldDescription fieldDescription);
/**
* A duplex factory for a type that both sets and gets a field value.
*/
@
HashCodeAndEqualsPlugin.
Enhance
class
Duplex implements
Factory {
/**
* The type of the accessor proxy.
*/
private final
TypeDescription proxyType;
/**
* The getter method.
*/
private final
MethodDescription.
InDefinedShape getterMethod;
/**
* The setter method.
*/
private final
MethodDescription.
InDefinedShape setterMethod;
/**
* Creates a new duplex factory.
*
* @param proxyType The type of the accessor proxy.
* @param getterMethod The getter method.
* @param setterMethod The setter method.
*/
protected
Duplex(
TypeDescription proxyType,
MethodDescription.
InDefinedShape getterMethod,
MethodDescription.
InDefinedShape setterMethod) {
this.
proxyType =
proxyType;
this.
getterMethod =
getterMethod;
this.
setterMethod =
setterMethod;
}
/**
* {@inheritDoc}
*/
public
FieldResolver resolve(
TypeDescription parameterType,
FieldDescription fieldDescription) {
if (
parameterType.
equals(
proxyType)) {
return new
ForGetterSetterPair(
proxyType,
getterMethod,
setterMethod);
} else {
throw new
IllegalStateException("Cannot use @FieldProxy on a non-installed type");
}
}
}
/**
* A simplex factory where field getters and setters both have their own type.
*/
@
HashCodeAndEqualsPlugin.
Enhance
class
Simplex implements
Factory {
/**
* The getter method.
*/
private final
MethodDescription.
InDefinedShape getterMethod;
/**
* The setter method.
*/
private final
MethodDescription.
InDefinedShape setterMethod;
/**
* Creates a simplex factory.
*
* @param getterMethod The getter method.
* @param setterMethod The setter method.
*/
protected
Simplex(
MethodDescription.
InDefinedShape getterMethod,
MethodDescription.
InDefinedShape setterMethod) {
this.
getterMethod =
getterMethod;
this.
setterMethod =
setterMethod;
}
/**
* {@inheritDoc}
*/
public
FieldResolver resolve(
TypeDescription parameterType,
FieldDescription fieldDescription) {
if (
parameterType.
equals(
getterMethod.
getDeclaringType())) {
return new
ForGetter(
getterMethod);
} else if (
parameterType.
equals(
setterMethod.
getDeclaringType())) {
return
fieldDescription.
isFinal()
?
Unresolved.
INSTANCE
: new
ForSetter(
setterMethod);
} else {
throw new
IllegalStateException("Cannot use @FieldProxy on a non-installed type");
}
}
}
}
/**
* An unresolved field resolver.
*/
enum
Unresolved implements
FieldResolver {
/**
* The singleton instance.
*/
INSTANCE;
/**
* {@inheritDoc}
*/
public boolean
isResolved() {
return false;
}
/**
* {@inheritDoc}
*/
public
TypeDescription getProxyType() {
throw new
IllegalStateException("Cannot read type for unresolved field resolver");
}
/**
* {@inheritDoc}
*/
public
DynamicType.
Builder<?>
apply(
DynamicType.
Builder<?>
builder,
FieldDescription fieldDescription,
Assigner assigner,
MethodAccessorFactory methodAccessorFactory) {
throw new
IllegalStateException("Cannot apply unresolved field resolver");
}
}
/**
* A field resolver for a getter accessor.
*/
@
HashCodeAndEqualsPlugin.
Enhance
class
ForGetter implements
FieldResolver {
/**
* The getter method.
*/
private final
MethodDescription.
InDefinedShape getterMethod;
/**
* Creates a new getter field resolver.
*
* @param getterMethod The getter method.
*/
protected
ForGetter(
MethodDescription.
InDefinedShape getterMethod) {
this.
getterMethod =
getterMethod;
}
/**
* {@inheritDoc}
*/
public boolean
isResolved() {
return true;
}
/**
* {@inheritDoc}
*/
public
TypeDescription getProxyType() {
return
getterMethod.
getDeclaringType();
}
/**
* {@inheritDoc}
*/
public
DynamicType.
Builder<?>
apply(
DynamicType.
Builder<?>
builder,
FieldDescription fieldDescription,
Assigner assigner,
MethodAccessorFactory methodAccessorFactory) {
return
builder.
method(
definedMethod(
is(
getterMethod))).
intercept(new
FieldGetter(
fieldDescription,
assigner,
methodAccessorFactory));
}
}
/**
* A field resolver for a setter accessor.
*/
@
HashCodeAndEqualsPlugin.
Enhance
class
ForSetter implements
FieldResolver {
/**
* The setter method.
*/
private final
MethodDescription.
InDefinedShape setterMethod;
/**
* Creates a new field resolver for a setter accessor.
*
* @param setterMethod The setter method.
*/
protected
ForSetter(
MethodDescription.
InDefinedShape setterMethod) {
this.
setterMethod =
setterMethod;
}
/**
* {@inheritDoc}
*/
public boolean
isResolved() {
return true;
}
/**
* {@inheritDoc}
*/
public
TypeDescription getProxyType() {
return
setterMethod.
getDeclaringType();
}
/**
* {@inheritDoc}
*/
public
DynamicType.
Builder<?>
apply(
DynamicType.
Builder<?>
builder,
FieldDescription fieldDescription,
Assigner assigner,
MethodAccessorFactory methodAccessorFactory) {
return
builder.
method(
is(
setterMethod)).
intercept(new
FieldSetter(
fieldDescription,
assigner,
methodAccessorFactory));
}
}
/**
* A field resolver for an accessor that both gets and sets a field value.
*/
@
HashCodeAndEqualsPlugin.
Enhance
class
ForGetterSetterPair implements
FieldResolver {
/**
* The type of the accessor proxy.
*/
private final
TypeDescription proxyType;
/**
* The getter method.
*/
private final
MethodDescription.
InDefinedShape getterMethod;
/**
* The setter method.
*/
private final
MethodDescription.
InDefinedShape setterMethod;
/**
* Creates a new field resolver for an accessor that both gets and sets a field value.
*
* @param proxyType The type of the accessor proxy.
* @param getterMethod The getter method.
* @param setterMethod The setter method.
*/
protected
ForGetterSetterPair(
TypeDescription proxyType,
MethodDescription.
InDefinedShape getterMethod,
MethodDescription.
InDefinedShape setterMethod) {
this.
proxyType =
proxyType;
this.
getterMethod =
getterMethod;
this.
setterMethod =
setterMethod;
}
/**
* {@inheritDoc}
*/
public boolean
isResolved() {
return true;
}
/**
* {@inheritDoc}
*/
public
TypeDescription getProxyType() {
return
proxyType;
}
/**
* {@inheritDoc}
*/
public
DynamicType.
Builder<?>
apply(
DynamicType.
Builder<?>
builder,
FieldDescription fieldDescription,
Assigner assigner,
MethodAccessorFactory methodAccessorFactory) {
return
builder
.
method(
is(
getterMethod)).
intercept(new
FieldGetter(
fieldDescription,
assigner,
methodAccessorFactory))
.
method(
is(
setterMethod)).
intercept(
fieldDescription.
isFinal()
?
ExceptionMethod.
throwing(
UnsupportedOperationException.class, "Cannot set final field " +
fieldDescription)
: new
FieldSetter(
fieldDescription,
assigner,
methodAccessorFactory));
}
}
}
/**
* Represents an implementation for implementing a proxy type constructor when a static field is accessed.
*/
protected enum
StaticFieldConstructor implements
Implementation {
/**
* The singleton instance.
*/
INSTANCE;
/**
* A reference of the {@link Object} type default constructor.
*/
private final
MethodDescription objectTypeDefaultConstructor;
/**
* Creates the constructor call singleton.
*/
StaticFieldConstructor() {
objectTypeDefaultConstructor =
TypeDescription.
OBJECT.
getDeclaredMethods().
filter(
isConstructor()).
getOnly();
}
/**
* {@inheritDoc}
*/
public
InstrumentedType prepare(
InstrumentedType instrumentedType) {
return
instrumentedType;
}
/**
* {@inheritDoc}
*/
public
ByteCodeAppender appender(
Target implementationTarget) {
return new
ByteCodeAppender.
Simple(
MethodVariableAccess.
loadThis(),
MethodInvocation.
invoke(
objectTypeDefaultConstructor),
MethodReturn.
VOID);
}
}
/**
* Represents an implementation for implementing a proxy type constructor when a non-static field is accessed.
*/
@
HashCodeAndEqualsPlugin.
Enhance
protected static class
InstanceFieldConstructor implements
Implementation {
/**
* The instrumented type from which a field is to be accessed.
*/
private final
TypeDescription instrumentedType;
/**
* Creates a new implementation for implementing a field accessor proxy's constructor when accessing
* a non-static field.
*
* @param instrumentedType The instrumented type from which a field is to be accessed.
*/
protected
InstanceFieldConstructor(
TypeDescription instrumentedType) {
this.
instrumentedType =
instrumentedType;
}
/**
* {@inheritDoc}
*/
public
InstrumentedType prepare(
InstrumentedType instrumentedType) {
return
instrumentedType.
withField(new
FieldDescription.
Token(
AccessorProxy.
FIELD_NAME,
Opcodes.
ACC_FINAL |
Opcodes.
ACC_PRIVATE,
this.
instrumentedType.
asGenericType()));
}
/**
* {@inheritDoc}
*/
public
ByteCodeAppender appender(
Target implementationTarget) {
return new
Appender(
implementationTarget);
}
/**
* An appender for implementing an
* {@link FieldProxy.Binder.InstanceFieldConstructor}.
*/
@
HashCodeAndEqualsPlugin.
Enhance
protected static class
Appender implements
ByteCodeAppender {
/**
* The field to be set within the constructor.
*/
private final
FieldDescription fieldDescription;
/**
* Creates a new appender.
*
* @param implementationTarget The implementation target of the current implementation.
*/
protected
Appender(
Target implementationTarget) {
fieldDescription =
implementationTarget.
getInstrumentedType()
.
getDeclaredFields()
.
filter((
named(
AccessorProxy.
FIELD_NAME)))
.
getOnly();
}
/**
* {@inheritDoc}
*/
public
Size apply(
MethodVisitor methodVisitor,
Context implementationContext,
MethodDescription instrumentedMethod) {
StackManipulation.
Size stackSize = new
StackManipulation.
Compound(
MethodVariableAccess.
loadThis(),
MethodInvocation.
invoke(
StaticFieldConstructor.
INSTANCE.
objectTypeDefaultConstructor),
MethodVariableAccess.
allArgumentsOf(
instrumentedMethod.
asDefined()).
prependThisReference(),
FieldAccess.
forField(
fieldDescription).
write(),
MethodReturn.
VOID
).
apply(
methodVisitor,
implementationContext);
return new
Size(
stackSize.
getMaximalSize(),
instrumentedMethod.
getStackSize());
}
}
}
/**
* Implementation for a getter method.
*/
@
HashCodeAndEqualsPlugin.
Enhance
protected static class
FieldGetter implements
Implementation {
/**
* The field that is being accessed.
*/
private final
FieldDescription fieldDescription;
/**
* The assigner to use.
*/
private final
Assigner assigner;
/**
* The accessed type's method accessor factory.
*/
private final
MethodAccessorFactory methodAccessorFactory;
/**
* Creates a new getter implementation.
*
* @param fieldDescription The field that is being accessed.
* @param assigner The assigner to use.
* @param methodAccessorFactory The accessed type's method accessor factory.
*/
protected
FieldGetter(
FieldDescription fieldDescription,
Assigner assigner,
MethodAccessorFactory methodAccessorFactory) {
this.
fieldDescription =
fieldDescription;
this.
assigner =
assigner;
this.
methodAccessorFactory =
methodAccessorFactory;
}
/**
* {@inheritDoc}
*/
public
InstrumentedType prepare(
InstrumentedType instrumentedType) {
return
instrumentedType;
}
/**
* {@inheritDoc}
*/
public
ByteCodeAppender appender(
Target implementationTarget) {
return new
Appender(
implementationTarget);
}
/**
* A byte code appender for a getter method.
*/
@
HashCodeAndEqualsPlugin.
Enhance(includeSyntheticFields = true)
protected class
Appender implements
ByteCodeAppender {
/**
* The generated accessor type.
*/
private final
TypeDescription typeDescription;
/**
* Creates a new appender for a setter method.
*
* @param implementationTarget The implementation target of the current instrumentation.
*/
protected
Appender(
Target implementationTarget) {
typeDescription =
implementationTarget.
getInstrumentedType();
}
/**
* {@inheritDoc}
*/
public
Size apply(
MethodVisitor methodVisitor,
Context implementationContext,
MethodDescription instrumentedMethod) {
MethodDescription getterMethod =
methodAccessorFactory.
registerGetterFor(
fieldDescription,
MethodAccessorFactory.
AccessType.
DEFAULT);
StackManipulation.
Size stackSize = new
StackManipulation.
Compound(
fieldDescription.
isStatic()
?
StackManipulation.
Trivial.
INSTANCE
: new
StackManipulation.
Compound(
MethodVariableAccess.
loadThis(),
FieldAccess.
forField(
typeDescription.
getDeclaredFields().
filter((
named(
AccessorProxy.
FIELD_NAME))).
getOnly()).
read()),
MethodInvocation.
invoke(
getterMethod),
assigner.
assign(
getterMethod.
getReturnType(),
instrumentedMethod.
getReturnType(),
Assigner.
Typing.
DYNAMIC),
MethodReturn.
of(
instrumentedMethod.
getReturnType().
asErasure())
).
apply(
methodVisitor,
implementationContext);
return new
Size(
stackSize.
getMaximalSize(),
instrumentedMethod.
getStackSize());
}
}
}
/**
* Implementation for a setter method.
*/
@
HashCodeAndEqualsPlugin.
Enhance
protected static class
FieldSetter implements
Implementation {
/**
* The field that is being accessed.
*/
private final
FieldDescription fieldDescription;
/**
* The assigner to use.
*/
private final
Assigner assigner;
/**
* The accessed type's method accessor factory.
*/
private final
MethodAccessorFactory methodAccessorFactory;
/**
* Creates a new setter implementation.
*
* @param fieldDescription The field that is being accessed.
* @param assigner The assigner to use.
* @param methodAccessorFactory The accessed type's method accessor factory.
*/
protected
FieldSetter(
FieldDescription fieldDescription,
Assigner assigner,
MethodAccessorFactory methodAccessorFactory) {
this.
fieldDescription =
fieldDescription;
this.
assigner =
assigner;
this.
methodAccessorFactory =
methodAccessorFactory;
}
/**
* {@inheritDoc}
*/
public
InstrumentedType prepare(
InstrumentedType instrumentedType) {
return
instrumentedType;
}
/**
* {@inheritDoc}
*/
public
ByteCodeAppender appender(
Target implementationTarget) {
return new
Appender(
implementationTarget);
}
/**
* A byte code appender for a setter method.
*/
@
HashCodeAndEqualsPlugin.
Enhance(includeSyntheticFields = true)
protected class
Appender implements
ByteCodeAppender {
/**
* The generated accessor type.
*/
private final
TypeDescription typeDescription;
/**
* Creates a new appender for a setter method.
*
* @param implementationTarget The implementation target of the current instrumentation.
*/
protected
Appender(
Target implementationTarget) {
typeDescription =
implementationTarget.
getInstrumentedType();
}
/**
* {@inheritDoc}
*/
public
Size apply(
MethodVisitor methodVisitor,
Context implementationContext,
MethodDescription instrumentedMethod) {
TypeDescription.
Generic parameterType =
instrumentedMethod.
getParameters().
get(0).
getType();
MethodDescription setterMethod =
methodAccessorFactory.
registerSetterFor(
fieldDescription,
MethodAccessorFactory.
AccessType.
DEFAULT);
StackManipulation.
Size stackSize = new
StackManipulation.
Compound(
fieldDescription.
isStatic()
?
StackManipulation.
Trivial.
INSTANCE
: new
StackManipulation.
Compound(
MethodVariableAccess.
loadThis(),
FieldAccess.
forField(
typeDescription.
getDeclaredFields()
.
filter((
named(
AccessorProxy.
FIELD_NAME))).
getOnly()).
read()),
MethodVariableAccess.
of(
parameterType).
loadFrom(1),
assigner.
assign(
parameterType,
setterMethod.
getParameters().
get(0).
getType(),
Assigner.
Typing.
DYNAMIC),
MethodInvocation.
invoke(
setterMethod),
MethodReturn.
VOID
).
apply(
methodVisitor,
implementationContext);
return new
Size(
stackSize.
getMaximalSize(),
instrumentedMethod.
getStackSize());
}
}
}
/**
* A proxy type for accessing a field either by a getter or a setter.
*/
@
HashCodeAndEqualsPlugin.
Enhance(includeSyntheticFields = true)
protected class
AccessorProxy implements
AuxiliaryType,
StackManipulation {
/**
* The name of the field that stores the accessed instance if any.
*/
protected static final
String FIELD_NAME = "instance";
/**
* The field that is being accessed.
*/
private final
FieldDescription fieldDescription;
/**
* The type which is accessed.
*/
private final
TypeDescription instrumentedType;
/**
* The field resolver to use.
*/
private final
FieldResolver fieldResolver;
/**
* The assigner to use.
*/
private final
Assigner assigner;
/**
* {@code true} if the generated proxy should be serializable.
*/
private final boolean
serializableProxy;
/**
* @param fieldDescription The field that is being accessed.
* @param instrumentedType The type which is accessed.
* @param fieldResolver The field resolver to use.
* @param assigner The assigner to use.
* @param serializableProxy {@code true} if the generated proxy should be serializable.
*/
protected
AccessorProxy(
FieldDescription fieldDescription,
TypeDescription instrumentedType,
FieldResolver fieldResolver,
Assigner assigner,
boolean
serializableProxy) {
this.
fieldDescription =
fieldDescription;
this.
instrumentedType =
instrumentedType;
this.
fieldResolver =
fieldResolver;
this.
assigner =
assigner;
this.
serializableProxy =
serializableProxy;
}
/**
* {@inheritDoc}
*/
public
DynamicType make(
String auxiliaryTypeName,
ClassFileVersion classFileVersion,
MethodAccessorFactory methodAccessorFactory) {
return
fieldResolver.
apply(new
ByteBuddy(
classFileVersion)
.
with(
TypeValidation.
DISABLED)
.
subclass(
fieldResolver.
getProxyType(),
ConstructorStrategy.
Default.
NO_CONSTRUCTORS)
.
name(
auxiliaryTypeName)
.
modifiers(
DEFAULT_TYPE_MODIFIER)
.
implement(
serializableProxy ? new
Class<?>[]{
Serializable.class} : new
Class<?>[0])
.
defineConstructor().
withParameters(
fieldDescription.
isStatic()
?
Collections.<
TypeDescription>
emptyList()
:
Collections.
singletonList(
instrumentedType))
.
intercept(
fieldDescription.
isStatic()
?
StaticFieldConstructor.
INSTANCE
: new
InstanceFieldConstructor(
instrumentedType)),
fieldDescription,
assigner,
methodAccessorFactory).
make();
}
/**
* {@inheritDoc}
*/
public boolean
isValid() {
return true;
}
/**
* {@inheritDoc}
*/
public
Size apply(
MethodVisitor methodVisitor,
Implementation.
Context implementationContext) {
TypeDescription auxiliaryType =
implementationContext.
register(this);
return new
Compound(
TypeCreation.
of(
auxiliaryType),
Duplication.
SINGLE,
fieldDescription.
isStatic()
?
Trivial.
INSTANCE
:
MethodVariableAccess.
loadThis(),
MethodInvocation.
invoke(
auxiliaryType.
getDeclaredMethods().
filter(
isConstructor()).
getOnly())
).
apply(
methodVisitor,
implementationContext);
}
}
}
}