com.ui4j.bytebuddy.instrumentation.MethodDelegation Maven / Gradle / Ivy
package com.ui4j.bytebuddy.instrumentation;
import com.ui4j.bytebuddy.instrumentation.method.MethodDescription;
import com.ui4j.bytebuddy.instrumentation.method.MethodList;
import com.ui4j.bytebuddy.instrumentation.method.MethodLookupEngine;
import com.ui4j.bytebuddy.instrumentation.method.bytecode.ByteCodeAppender;
import com.ui4j.bytebuddy.instrumentation.method.bytecode.bind.*;
import com.ui4j.bytebuddy.instrumentation.method.bytecode.bind.annotation.*;
import com.ui4j.bytebuddy.instrumentation.method.bytecode.stack.Duplication;
import com.ui4j.bytebuddy.instrumentation.method.bytecode.stack.StackManipulation;
import com.ui4j.bytebuddy.instrumentation.method.bytecode.stack.TypeCreation;
import com.ui4j.bytebuddy.instrumentation.method.bytecode.stack.assign.Assigner;
import com.ui4j.bytebuddy.instrumentation.method.bytecode.stack.assign.primitive.PrimitiveTypeAwareAssigner;
import com.ui4j.bytebuddy.instrumentation.method.bytecode.stack.assign.primitive.VoidAwareAssigner;
import com.ui4j.bytebuddy.instrumentation.method.bytecode.stack.assign.reference.ReferenceTypeAwareAssigner;
import com.ui4j.bytebuddy.instrumentation.method.bytecode.stack.member.FieldAccess;
import com.ui4j.bytebuddy.instrumentation.method.bytecode.stack.member.MethodVariableAccess;
import com.ui4j.bytebuddy.instrumentation.type.InstrumentedType;
import com.ui4j.bytebuddy.instrumentation.type.TypeDescription;
import com.ui4j.bytebuddy.matcher.ElementMatcher;
import com.ui4j.bytebuddy.jar.asm.MethodVisitor;
import com.ui4j.bytebuddy.jar.asm.Opcodes;
import java.util.Arrays;
import java.util.List;
import static com.ui4j.bytebuddy.matcher.ElementMatchers.*;
import static com.ui4j.bytebuddy.utility.ByteBuddyCommons.*;
/**
* This instrumentation delegates an method call to another method which can either be {@code static} by providing
* a reference to a {@link java.lang.Class} or an instance method when another object is provided. The potential
* targets of the method delegation can further be filtered by applying a filter. The method delegation can be
* customized by invoking the {@code MethodDelegation}'s several builder methods.
* Without any customization, the method delegation will work as follows:
* Binding an instrumented method to a given delegate method
*
* A method will be bound parameter by parameter. Considering a method {@code Foo#bar} being bound to a method
* {@code Qux#baz}, the method delegation will be decided on basis of the following annotations:
*
* - {@link com.ui4j.bytebuddy.instrumentation.method.bytecode.bind.annotation.Argument}:
* This annotation will bind the {@code n}-th parameter of {@code Foo#bar} to that parameter of {@code Qux#baz}that
* is annotated with this annotation where {@code n} is the obligatory argument of the {@code @Argument} annotation.
* - {@link com.ui4j.bytebuddy.instrumentation.method.bytecode.bind.annotation.AllArguments}:
* This annotation will assign a collection of all parameters of {@code Foo#bar} to that parameter of {@code Qux#baz}
* that is annotated with {@code AllArguments}.
* - {@link com.ui4j.bytebuddy.instrumentation.method.bytecode.bind.annotation.This}: A parameter
* of {@code Qux#baz} that is annotated with {@code This} will be assigned the instance that is instrumented for
* a non-static method.
* - {@link com.ui4j.bytebuddy.instrumentation.method.bytecode.bind.annotation.Super}: A parameter that is annotated with
* this annotation is assigned a proxy that allows calling an instrumented type's super methods.
* - {@link com.ui4j.bytebuddy.instrumentation.method.bytecode.bind.annotation.Default}: A parameter that is annotated with
* this annotation is assigned a proxy that allows calling an instrumented type's directly implemented interfaces'
* default methods.
* - {@link com.ui4j.bytebuddy.instrumentation.method.bytecode.bind.annotation.SuperCall}: A parameter
* of {@code Qux#baz} that is annotated with {@code SuperCall} will be assigned an instance of a type implementing both
* {@link java.lang.Runnable} and {@link java.util.concurrent.Callable} which will invoke the instrumented method on the
* invocation of either interface's method. The call is made using the original arguments of the method invocation.
* The return value is only emitted for the {@link java.util.concurrent.Callable#call()} method which additionally
* requires to catch any unchecked exceptions that might be thrown by the original method's implementation. If a
* source method is abstract, using this annotation excludes the method with this parameter annotation from being bound
* to this source method.
*
* - {@link com.ui4j.bytebuddy.instrumentation.method.bytecode.bind.annotation.DefaultCall}:
* This annotation is similar to the {@link com.ui4j.bytebuddy.instrumentation.method.bytecode.bind.annotation.SuperCall}
* annotation but it invokes a default method that is compatible to this method. If a source method does not represent
* a default method, using this annotation excludes the method with this parameter annotation from being bound to this
* source method.
* - {@link com.ui4j.bytebuddy.instrumentation.method.bytecode.bind.annotation.Origin}: A parameter of
* {@code Qux#baz} that is annotated with {@code Origin} is assigned a reference to either a {@link java.lang.reflect.Method}
* or a {@link java.lang.Class} instance. A {@code Method}-typed parameter is assigned a reference to the original method that
* is overriden. A {@code Class}-typed parameter is assigned the type of the caller.
* - {@link com.ui4j.bytebuddy.instrumentation.method.bytecode.bind.annotation.Empty}: Assigns the parameter type's
* default value, i.e. {@code null} for a reference type or zero for primitive types. This is an opportunity to
* ignore a parameter.
* - {@link com.ui4j.bytebuddy.instrumentation.method.bytecode.bind.annotation.Pipe}: A parameter that is annotated
* with this annotation is assigned a proxy for forwarding the source method invocation to another instance of the
* same type as the declaring type of the intercepted method. This annotation needs to be installed and explicitly
* registered before it can be used. See the {@link com.ui4j.bytebuddy.instrumentation.method.bytecode.bind.annotation.Pipe}
* annotation's documentation for further information on how this can be done.
* - {@link com.ui4j.bytebuddy.instrumentation.method.bytecode.bind.annotation.Morph}: The morph annotation is similar to
* the {@link com.ui4j.bytebuddy.instrumentation.method.bytecode.bind.annotation.SuperCall} annotation but allows to
* explicitly define and therewith alter the arguments that are handed to the super method. This annotation needs
* to be installed and explicitly registered before it can be used. See the documentation to the annotation for
* further information.
* - {@link com.ui4j.bytebuddy.instrumentation.method.bytecode.bind.annotation.Field}: Allows to access fields via getter
* and setter proxies. This annotation needs to be installed and explicitly registered before it can be used.
* Note that any field access requires boxing such that a use of {@link com.ui4j.bytebuddy.instrumentation.FieldAccessor} in
* combination with {@link com.ui4j.bytebuddy.instrumentation.MethodDelegation#andThen(Instrumentation)} might be a more
* performant alternative for implementing field getters and setters.
*
* If a method is not annotated with any of the above methods, it will be treated as if it was annotated
* {@link com.ui4j.bytebuddy.instrumentation.method.bytecode.bind.annotation.Argument} using the next
* unbound parameter index of the source method as its parameter. This means that a method
* {@code Qux#baz(@Argument(2) Object p1, Object p2, @Argument(0) Object p3} would be treated as if {@code p2} was annotated
* with {@code @Argument(1)}.
*
* In addition, the {@link com.ui4j.bytebuddy.instrumentation.method.bytecode.bind.annotation.RuntimeType}
* annotation can instruct a parameter to be bound by a
* {@link com.ui4j.bytebuddy.instrumentation.method.bytecode.stack.assign.Assigner} with considering the
* runtime type of the parameter.
*
* Selecting among different methods that can be used for binding a method
* of the instrumented type
*
* When deciding between two methods {@code Foo#bar} and {@code Foo#qux} that could both be used to delegating a
* method call, the following consideration is applied in the given order:
*
* - {@link com.ui4j.bytebuddy.instrumentation.method.bytecode.bind.annotation.BindingPriority}:
* A method that is annotated with this annotation is given a specific priority where the default priority is set
* to {@link com.ui4j.bytebuddy.instrumentation.method.bytecode.bind.annotation.BindingPriority#DEFAULT}
* for non-annotated method. A method with a higher priority is considered a better target for delegation.
* - {@link com.ui4j.bytebuddy.instrumentation.method.bytecode.bind.DeclaringTypeResolver}:
* If a target method is declared by a more specific type than another method, the method with the most specific
* type is bound.
* - {@link com.ui4j.bytebuddy.instrumentation.method.bytecode.bind.MethodNameEqualityResolver}:
* If a source method {@code Baz#qux} is the source method, it will rather be assigned to {@code Foo#qux} because
* of their equal names. Similar names and case-insensitive equality are not considered.
* - {@link com.ui4j.bytebuddy.instrumentation.method.bytecode.bind.ArgumentTypeResolver}:
* The most specific type resolver will consider all bindings that are using the
* {@link com.ui4j.bytebuddy.instrumentation.method.bytecode.bind.annotation.Argument}
* annotation for resolving a binding conflict. In this context, the resolution will equal the most-specific
* type resolution that is performed by the Java compiler. This means that a source method {@code Bar#baz(String)}
* will rather be bound to a method {@code Foo#bar(String)} than {@code Foo#qux(Object)} because the {@code String}
* type is more specific than the {@code Object} type. If two methods are equally adequate by their parameter types,
* then the method with the higher numbers of {@code @Argument} annotated parameters is considered as the better
* delegation target.
* - {@link com.ui4j.bytebuddy.instrumentation.method.bytecode.bind.ParameterLengthResolver}:
* If a target methods has a higher number of total parameters that were successfully bound, the method with
* the higher number will be considered as the better delegation target.
*
* Additionally, if a method is annotated by
* {@link com.ui4j.bytebuddy.instrumentation.method.bytecode.bind.annotation.IgnoreForBinding},
* it is never considered as a target for a method delegation.
*/
public class MethodDelegation implements Instrumentation {
/**
* The error message for the exception to be thrown if no method for delegation can be identified.
*/
private static final String NO_METHODS_ERROR_MESSAGE = "The target type does not define any methods for delegation";
/**
* The instrumentation delegate for this method delegation.
*/
private final InstrumentationDelegate instrumentationDelegate;
/**
* A list of {@link com.ui4j.bytebuddy.instrumentation.method.bytecode.bind.annotation.TargetMethodAnnotationDrivenBinder.ParameterBinder}s
* to be used by this method delegation.
*/
private final List> parameterBinders;
/**
* The {@link com.ui4j.bytebuddy.instrumentation.method.bytecode.bind.annotation.TargetMethodAnnotationDrivenBinder.DefaultsProvider}
* to be used by this method delegation.
*/
private final TargetMethodAnnotationDrivenBinder.DefaultsProvider defaultsProvider;
/**
* The termination handler to apply.
*/
private final TargetMethodAnnotationDrivenBinder.TerminationHandler terminationHandler;
/**
* The {@link com.ui4j.bytebuddy.instrumentation.method.bytecode.bind.MethodDelegationBinder.AmbiguityResolver}
* to be used by this method delegation.
*/
private final MethodDelegationBinder.AmbiguityResolver ambiguityResolver;
/**
* The {@link com.ui4j.bytebuddy.instrumentation.method.bytecode.stack.assign.Assigner} to be used by this method delegation.
*/
private final Assigner assigner;
/**
* A list of methods to be considered as target by this method delegation.
*/
private final MethodList targetMethodCandidates;
/**
* Creates a new method delegation.
*
* @param instrumentationDelegate The instrumentation delegate to use by this method delegator.
* @param parameterBinders The parameter binders to use by this method delegator.
* @param defaultsProvider The defaults provider to use by this method delegator.
* @param terminationHandler The termination handler to apply.
* @param ambiguityResolver The ambiguity resolver to use by this method delegator.
* @param assigner The assigner to be supplied by this method delegator.
* @param targetMethodCandidates A list of methods that should be considered as possible binding targets by
* this method delegator.
*/
protected MethodDelegation(InstrumentationDelegate instrumentationDelegate,
List> parameterBinders,
TargetMethodAnnotationDrivenBinder.DefaultsProvider defaultsProvider,
TargetMethodAnnotationDrivenBinder.TerminationHandler terminationHandler,
MethodDelegationBinder.AmbiguityResolver ambiguityResolver,
Assigner assigner,
MethodList targetMethodCandidates) {
this.instrumentationDelegate = instrumentationDelegate;
this.parameterBinders = parameterBinders;
this.defaultsProvider = defaultsProvider;
this.terminationHandler = terminationHandler;
this.ambiguityResolver = ambiguityResolver;
this.assigner = assigner;
this.targetMethodCandidates = isNotEmpty(targetMethodCandidates, NO_METHODS_ERROR_MESSAGE);
}
/**
* Creates an instrumentation where only {@code static} methods of the given type are considered as binding targets.
*
* @param type The type containing the {@code static} methods for binding.
* @return A method delegation instrumentation to the given {@code static} methods.
*/
public static MethodDelegation to(Class> type) {
return to(new TypeDescription.ForLoadedType(nonNull(type)));
}
/**
* Creates an instrumentation where only {@code static} methods of the given type are considered as binding targets.
*
* @param typeDescription The type containing the {@code static} methods for binding.
* @return A method delegation instrumentation to the given {@code static} methods.
*/
public static MethodDelegation to(TypeDescription typeDescription) {
if (nonNull(typeDescription).isInterface()) {
throw new IllegalArgumentException("Cannot delegate to interface " + typeDescription);
} else if (typeDescription.isArray()) {
throw new IllegalArgumentException("Cannot delegate to array " + typeDescription);
} else if (typeDescription.isPrimitive()) {
throw new IllegalArgumentException("Cannot delegate to primitive " + typeDescription);
}
return new MethodDelegation(InstrumentationDelegate.ForStaticMethod.INSTANCE,
defaultParameterBinders(),
defaultDefaultsProvider(),
TargetMethodAnnotationDrivenBinder.TerminationHandler.Returning.INSTANCE,
defaultAmbiguityResolver(),
defaultAssigner(),
typeDescription.getDeclaredMethods().filter(isStatic().and(not(isPrivate()))));
}
/**
* Creates an instrumentation where only instance methods of the given object are considered as binding targets.
* This method will never bind to constructors but will consider methods that are defined in super types. Note
* that this includes methods that were defined by the {@link java.lang.Object} class. You can narrow this default
* selection by explicitly selecting methods with calling the
* {@link com.ui4j.bytebuddy.instrumentation.MethodDelegation#filter(com.ui4j.bytebuddy.matcher.ElementMatcher)}
* method on the returned method delegation as for example:
* MethodDelegation.to(new Foo()).filter(MethodMatchers.not(isDeclaredBy(Object.class)));
* which will result in a delegation to Foo
where no methods of {@link java.lang.Object} are considered
* for delegation.
*
* @param delegate A delegate instance which will be injected by a
* {@link com.ui4j.bytebuddy.instrumentation.LoadedTypeInitializer}. All intercepted method calls are
* then delegated to this instance.
* @return A method delegation instrumentation to the given instance methods.
*/
public static MethodDelegation to(Object delegate) {
return to(nonNull(delegate), defaultMethodLookupEngine());
}
/**
* Identical to {@link com.ui4j.bytebuddy.instrumentation.MethodDelegation#to(Object)} but uses an explicit
* {@link com.ui4j.bytebuddy.instrumentation.method.MethodLookupEngine}.
*
* @param delegate A delegate instance which will be injected by a
* {@link com.ui4j.bytebuddy.instrumentation.LoadedTypeInitializer}. All intercepted method
* calls are then delegated to this instance.
* @param methodLookupEngine The method lookup engine to use.
* @return A method delegation instrumentation to the given instance methods.
*/
public static MethodDelegation to(Object delegate, MethodLookupEngine methodLookupEngine) {
return new MethodDelegation(new InstrumentationDelegate.ForStaticField(nonNull(delegate)),
defaultParameterBinders(),
defaultDefaultsProvider(),
TargetMethodAnnotationDrivenBinder.TerminationHandler.Returning.INSTANCE,
defaultAmbiguityResolver(),
defaultAssigner(),
methodLookupEngine.process(new TypeDescription.ForLoadedType(delegate.getClass()))
.getInvokableMethods()
.filter(not(isStatic().or(isPrivate()).or(isConstructor()))));
}
/**
* Creates an instrumentation where only instance methods of the given object are considered as binding targets.
* This method will never bind to constructors but will consider methods that are defined in super types. Note
* that this includes methods that were defined by the {@link java.lang.Object} class. You can narrow this default
* selection by explicitly selecting methods with calling the
* {@link com.ui4j.bytebuddy.instrumentation.MethodDelegation#filter(com.ui4j.bytebuddy.matcher.ElementMatcher)}
* method on the returned method delegation as for example:
* MethodDelegation.to(new Foo()).filter(MethodMatchers.not(isDeclaredBy(Object.class)));
* which will result in a delegation to Foo
where no methods of {@link java.lang.Object} are considered
* for delegation.
*
* @param delegate A delegate instance which will be injected by a
* {@link com.ui4j.bytebuddy.instrumentation.LoadedTypeInitializer}. All intercepted method calls are
* then delegated to this instance.
* @param fieldName The name of the field for storing the delegate instance.
* @return A method delegation instrumentation to the given {@code static} methods.
*/
public static MethodDelegation to(Object delegate, String fieldName) {
return to(delegate, fieldName, defaultMethodLookupEngine());
}
/**
* Identical to {@link com.ui4j.bytebuddy.instrumentation.MethodDelegation#to(Object, java.lang.String)} but uses an
* explicit {@link com.ui4j.bytebuddy.instrumentation.method.MethodLookupEngine}.
*
* @param delegate A delegate instance which will be injected by a type initializer and to which all intercepted
* method calls are delegated to.
* @param fieldName The name of the field for storing the delegate instance.
* @param methodLookupEngine The method lookup engine to use.
* @return A method delegation instrumentation to the given {@code static} methods.
*/
public static MethodDelegation to(Object delegate, String fieldName, MethodLookupEngine methodLookupEngine) {
return new MethodDelegation(
new InstrumentationDelegate.ForStaticField(nonNull(delegate), isValidIdentifier(fieldName)),
defaultParameterBinders(),
defaultDefaultsProvider(),
TargetMethodAnnotationDrivenBinder.TerminationHandler.Returning.INSTANCE,
defaultAmbiguityResolver(),
defaultAssigner(),
methodLookupEngine.process(new TypeDescription.ForLoadedType(delegate.getClass()))
.getInvokableMethods()
.filter(not(isStatic().or(isPrivate()).or(isConstructor()))));
}
/**
* Creates an instrumentation where method calls are delegated to an instance that is manually stored in a field
* {@code fieldName} that is defined for the instrumented type. The field belongs to any instance of the instrumented
* type and must be set manually by the user of the instrumented class. Note that this prevents interception of
* method calls within the constructor of the instrumented class which will instead result in a
* {@link java.lang.NullPointerException}. Note that this includes methods that were defined by the
* {@link java.lang.Object} class. You can narrow this default selection by explicitly selecting methods with
* calling the
* {@link com.ui4j.bytebuddy.instrumentation.MethodDelegation#filter(com.ui4j.bytebuddy.matcher.ElementMatcher)}
* method on the returned method delegation as for example:
* MethodDelegation.to(new Foo()).filter(MethodMatchers.not(isDeclaredBy(Object.class)));
* which will result in a delegation to Foo
where no methods of {@link java.lang.Object} are considered
* for delegation.
*
* The field is typically accessed by reflection or by defining an accessor on the instrumented type.
*
* @param type The type of the delegate and the field.
* @param fieldName The name of the field.
* @return A method delegation that intercepts method calls by delegating to method calls on the given instance.
*/
public static MethodDelegation toInstanceField(Class> type, String fieldName) {
return toInstanceField(new TypeDescription.ForLoadedType(nonNull(type)), fieldName);
}
/**
* Creates an instrumentation where method calls are delegated to an instance that is manually stored in a field
* {@code fieldName} that is defined for the instrumented type. The field belongs to any instance of the instrumented
* type and must be set manually by the user of the instrumented class. Note that this prevents interception of
* method calls within the constructor of the instrumented class which will instead result in a
* {@link java.lang.NullPointerException}. Note that this includes methods that were defined by the
* {@link java.lang.Object} class. You can narrow this default selection by explicitly selecting methods with
* calling the
* {@link com.ui4j.bytebuddy.instrumentation.MethodDelegation#filter(com.ui4j.bytebuddy.matcher.ElementMatcher)}
* method on the returned method delegation as for example:
* MethodDelegation.to(new Foo()).filter(MethodMatchers.not(isDeclaredBy(Object.class)));
* which will result in a delegation to Foo
where no methods of {@link java.lang.Object} are considered
* for delegation.
*
* The field is typically accessed by reflection or by defining an accessor on the instrumented type.
*
* @param typeDescription The type of the delegate and the field.
* @param fieldName The name of the field.
* @return A method delegation that intercepts method calls by delegating to method calls on the given instance.
*/
public static MethodDelegation toInstanceField(TypeDescription typeDescription, String fieldName) {
return toInstanceField(nonNull(typeDescription), isValidIdentifier(fieldName), defaultMethodLookupEngine());
}
/**
* Identical to {@link com.ui4j.bytebuddy.instrumentation.MethodDelegation#toInstanceField(Class, String)} but uses an
* explicit {@link com.ui4j.bytebuddy.instrumentation.method.MethodLookupEngine}.
*
* @param type The type of the delegate and the field.
* @param fieldName The name of the field.
* @param methodLookupEngine The method lookup engine to use.
* @return A method delegation that intercepts method calls by delegating to method calls on the given instance.
*/
public static MethodDelegation toInstanceField(Class> type, String fieldName, MethodLookupEngine methodLookupEngine) {
return toInstanceField(new TypeDescription.ForLoadedType(nonNull(type)), fieldName, methodLookupEngine);
}
/**
* Identical to {@link com.ui4j.bytebuddy.instrumentation.MethodDelegation#toInstanceField(Class, String)} but uses an
* explicit {@link com.ui4j.bytebuddy.instrumentation.method.MethodLookupEngine}.
*
* @param typeDescription The type of the delegate and the field.
* @param fieldName The name of the field.
* @param methodLookupEngine The method lookup engine to use.
* @return A method delegation that intercepts method calls by delegating to method calls on the given instance.
*/
public static MethodDelegation toInstanceField(TypeDescription typeDescription, String fieldName, MethodLookupEngine methodLookupEngine) {
return new MethodDelegation(
new InstrumentationDelegate.ForInstanceField(nonNull(typeDescription), isValidIdentifier(fieldName)),
defaultParameterBinders(),
defaultDefaultsProvider(),
TargetMethodAnnotationDrivenBinder.TerminationHandler.Returning.INSTANCE,
defaultAmbiguityResolver(),
defaultAssigner(),
methodLookupEngine.process(typeDescription)
.getInvokableMethods()
.filter(not(isStatic().or(isPrivate()).or(isConstructor()))));
}
/**
* Creates an instrumentation where method calls are delegated to constructor calls on the given type. As a result,
* the return values of all instrumented methods must be assignable to
*
* @param type The type that should be constructed by the instrumented methods.
* @return An instrumentation that creates instances of the given type as its result.
*/
public static MethodDelegation toConstructor(Class> type) {
return toConstructor(new TypeDescription.ForLoadedType(nonNull(type)));
}
/**
* Creates an instrumentation where method calls are delegated to constructor calls on the given type. As a result,
* the return values of all instrumented methods must be assignable to
*
* @param typeDescription The type that should be constructed by the instrumented methods.
* @return An instrumentation that creates instances of the given type as its result.
*/
public static MethodDelegation toConstructor(TypeDescription typeDescription) {
return new MethodDelegation(new InstrumentationDelegate.ForConstruction(nonNull(typeDescription)),
defaultParameterBinders(),
defaultDefaultsProvider(),
TargetMethodAnnotationDrivenBinder.TerminationHandler.Returning.INSTANCE,
defaultAmbiguityResolver(),
defaultAssigner(),
typeDescription.getDeclaredMethods().filter(isConstructor()));
}
/**
* Returns the default parameter binders to be used if not explicitly specified.
*
* @return The default parameter binders to be used if not explicitly specified.
*/
private static List> defaultParameterBinders() {
return Arrays.>asList(Argument.Binder.INSTANCE,
AllArguments.Binder.INSTANCE,
Origin.Binder.INSTANCE,
This.Binder.INSTANCE,
Super.Binder.INSTANCE,
Default.Binder.INSTANCE,
SuperCall.Binder.INSTANCE,
DefaultCall.Binder.INSTANCE,
Empty.Binder.INSTANCE);
}
/**
* Returns the defaults provider that is to be used if no other is specified explicitly.
*
* @return The defaults provider that is to be used if no other is specified explicitly.
*/
private static TargetMethodAnnotationDrivenBinder.DefaultsProvider defaultDefaultsProvider() {
return Argument.NextUnboundAsDefaultsProvider.INSTANCE;
}
/**
* Returns the ambiguity resolver that is to be used if no other is specified explicitly.
*
* @return The ambiguity resolver that is to be used if no other is specified explicitly.
*/
private static MethodDelegationBinder.AmbiguityResolver defaultAmbiguityResolver() {
return MethodDelegationBinder.AmbiguityResolver.Chain.of(BindingPriority.Resolver.INSTANCE,
DeclaringTypeResolver.INSTANCE,
ArgumentTypeResolver.INSTANCE,
MethodNameEqualityResolver.INSTANCE,
ParameterLengthResolver.INSTANCE);
}
/**
* Returns the assigner that is to be used if no other is specified explicitly.
*
* @return The assigner that is to be used if no other is specified explicitly.
*/
private static Assigner defaultAssigner() {
return new VoidAwareAssigner(new PrimitiveTypeAwareAssigner(ReferenceTypeAwareAssigner.INSTANCE));
}
/**
* Returns the method lookup engine that is to be used if no other is specified explicitly.
*
* @return The method lookup engine that is to be used if no other is specified explicitly.
*/
private static MethodLookupEngine defaultMethodLookupEngine() {
return new MethodLookupEngine.Default(MethodLookupEngine.Default.DefaultMethodLookup.DISABLED);
}
/**
* Defines an parameter binder to be appended to the already defined parameter binders.
*
* @param parameterBinder The parameter binder to append to the already defined parameter binders.
* @return A method delegation instrumentation that makes use of the given parameter binder.
*/
public MethodDelegation appendParameterBinder(TargetMethodAnnotationDrivenBinder.ParameterBinder> parameterBinder) {
return new MethodDelegation(instrumentationDelegate,
join(parameterBinders, nonNull(parameterBinder)),
defaultsProvider,
terminationHandler,
ambiguityResolver,
assigner,
targetMethodCandidates);
}
/**
* Defines a number of parameter binders to be appended to be used by this method delegation.
*
* @param parameterBinder The parameter binders to use by this parameter binders.
* @return A method delegation instrumentation that makes use of the given parameter binders.
*/
public MethodDelegation defineParameterBinder(TargetMethodAnnotationDrivenBinder.ParameterBinder>... parameterBinder) {
return new MethodDelegation(instrumentationDelegate,
Arrays.asList(nonNull(parameterBinder)),
defaultsProvider,
terminationHandler,
ambiguityResolver,
assigner,
targetMethodCandidates);
}
/**
* A provider for annotation instances on values that are not explicitly annotated.
*
* @param defaultsProvider The defaults provider to use.
* @return A method delegation instrumentation that makes use of the given defaults provider.
*/
public MethodDelegation withDefaultsProvider(TargetMethodAnnotationDrivenBinder.DefaultsProvider defaultsProvider) {
return new MethodDelegation(instrumentationDelegate,
parameterBinders,
nonNull(defaultsProvider),
terminationHandler,
ambiguityResolver,
assigner,
targetMethodCandidates);
}
/**
* Defines an ambiguity resolver to be appended to the already defined ambiguity resolver for resolving binding conflicts.
*
* @param ambiguityResolver The ambiguity resolver to append to the already defined ambiguity resolvers.
* @return A method delegation instrumentation that makes use of the given ambiguity resolver.
*/
public MethodDelegation appendAmbiguityResolver(MethodDelegationBinder.AmbiguityResolver ambiguityResolver) {
return defineAmbiguityResolver(MethodDelegationBinder.AmbiguityResolver.Chain
.of(this.ambiguityResolver, nonNull(ambiguityResolver)));
}
/**
* Defines an ambiguity resolver to be used for resolving binding conflicts.
*
* @param ambiguityResolver The ambiguity resolver to use exclusively.
* @return A method delegation instrumentation that makes use of the given ambiguity resolver.
*/
public MethodDelegation defineAmbiguityResolver(MethodDelegationBinder.AmbiguityResolver... ambiguityResolver) {
return new MethodDelegation(instrumentationDelegate,
parameterBinders,
defaultsProvider,
terminationHandler,
MethodDelegationBinder.AmbiguityResolver.Chain.of(nonNull(ambiguityResolver)),
assigner,
targetMethodCandidates);
}
/**
* Applies an assigner to the method delegation that is used for assigning method return and parameter types.
*
* @param assigner The assigner to apply.
* @return A method delegation instrumentation that makes use of the given designer.
*/
public MethodDelegation withAssigner(Assigner assigner) {
return new MethodDelegation(instrumentationDelegate,
parameterBinders,
defaultsProvider,
terminationHandler,
ambiguityResolver,
nonNull(assigner),
targetMethodCandidates);
}
/**
* Applies a filter to target methods that are eligible for delegation.
*
* @param methodMatcher A filter where only methods that match the filter are considered for delegation.
* @return A method delegation with the filter applied.
*/
public MethodDelegation filter(ElementMatcher super MethodDescription> methodMatcher) {
return new MethodDelegation(instrumentationDelegate,
parameterBinders,
defaultsProvider,
terminationHandler,
ambiguityResolver,
assigner,
isNotEmpty(targetMethodCandidates.filter(nonNull(methodMatcher)), NO_METHODS_ERROR_MESSAGE));
}
/**
* Appends another {@link com.ui4j.bytebuddy.instrumentation.Instrumentation} to a method delegation. The return
* value of the delegation target is dropped such that the given {@code instrumentation} becomes responsible for
* returning from the method instead. However, if an exception is thrown from the interception method, this
* exception is not catched and the chained instrumentation is never applied. Note that this changes the binding
* semantics as the target method's return value is not longer considered what might change the binding target.
*
* @param instrumentation The instrumentation to apply after the delegation.
* @return An instrumentation that represents this chained instrumentation application.
*/
public Instrumentation andThen(Instrumentation instrumentation) {
return new Compound(new MethodDelegation(instrumentationDelegate,
parameterBinders,
defaultsProvider,
TargetMethodAnnotationDrivenBinder.TerminationHandler.Dropping.INSTANCE,
ambiguityResolver,
assigner,
targetMethodCandidates), nonNull(instrumentation));
}
@Override
public InstrumentedType prepare(InstrumentedType instrumentedType) {
return instrumentationDelegate.prepare(instrumentedType);
}
@Override
public ByteCodeAppender appender(Target instrumentationTarget) {
MethodList methodList = this.targetMethodCandidates.filter(isVisibleTo(instrumentationTarget.getTypeDescription()));
if (methodList.size() == 0) {
throw new IllegalStateException("No bindable method is visible to " + instrumentationTarget.getTypeDescription());
}
return new Appender(instrumentationDelegate.getPreparingStackAssignment(instrumentationTarget.getTypeDescription()),
instrumentationTarget,
methodList,
new MethodDelegationBinder.Processor(new TargetMethodAnnotationDrivenBinder(
parameterBinders,
defaultsProvider,
terminationHandler,
assigner,
instrumentationDelegate.getMethodInvoker(instrumentationTarget.getTypeDescription())
), ambiguityResolver)
);
}
@Override
public boolean equals(Object other) {
if (this == other) return true;
if (other == null || getClass() != other.getClass()) return false;
MethodDelegation that = (MethodDelegation) other;
return ambiguityResolver.equals(that.ambiguityResolver)
&& assigner.equals(that.assigner)
&& defaultsProvider.equals(that.defaultsProvider)
&& terminationHandler.equals(that.terminationHandler)
&& instrumentationDelegate.equals(that.instrumentationDelegate)
&& targetMethodCandidates.equals(that.targetMethodCandidates)
&& parameterBinders.equals(that.parameterBinders);
}
@Override
public int hashCode() {
int result = instrumentationDelegate.hashCode();
result = 31 * result + parameterBinders.hashCode();
result = 31 * result + defaultsProvider.hashCode();
result = 31 * result + terminationHandler.hashCode();
result = 31 * result + ambiguityResolver.hashCode();
result = 31 * result + assigner.hashCode();
result = 31 * result + targetMethodCandidates.hashCode();
return result;
}
@Override
public String toString() {
return "MethodDelegation{" +
"instrumentationDelegate=" + instrumentationDelegate +
", parameterBinders=" + parameterBinders +
", defaultsProvider=" + defaultsProvider +
", terminationHandler=" + terminationHandler +
", ambiguityResolver=" + ambiguityResolver +
", assigner=" + assigner +
", targetMethodCandidates=" + targetMethodCandidates +
'}';
}
/**
* An instrumentation delegate is responsible for executing the actual method delegation.
*/
protected static interface InstrumentationDelegate {
/**
* Prepares the instrumented type.
*
* @param instrumentedType The instrumented type to be prepared.
* @return The instrumented type after it was prepared.
*/
InstrumentedType prepare(InstrumentedType instrumentedType);
/**
* Returns the stack manipulation responsible for preparing the instance representing the instrumentation.
*
* @param instrumentedType A description of the instrumented type to which the instrumentation is applied.
* @return A stack manipulation representing the preparation.
*/
StackManipulation getPreparingStackAssignment(TypeDescription instrumentedType);
/**
* Returns the method invoker responsible for invoking the delegation method.
*
* @param instrumentedType The instrumented type to which the instrumentation is applied.
* @return A method invoker responsible for invoking the delegation method.
*/
MethodDelegationBinder.MethodInvoker getMethodInvoker(TypeDescription instrumentedType);
/**
* An instrumentation applied to a static method.
*/
static enum ForStaticMethod implements InstrumentationDelegate {
/**
* The singleton instance.
*/
INSTANCE;
@Override
public InstrumentedType prepare(InstrumentedType instrumentedType) {
return instrumentedType;
}
@Override
public StackManipulation getPreparingStackAssignment(TypeDescription instrumentedType) {
return StackManipulation.LegalTrivial.INSTANCE;
}
@Override
public MethodDelegationBinder.MethodInvoker getMethodInvoker(TypeDescription instrumentedType) {
return MethodDelegationBinder.MethodInvoker.Simple.INSTANCE;
}
}
/**
* An instrumentation applied on a static field.
*/
static class ForStaticField implements InstrumentationDelegate {
/**
* The name prefix for the {@code static} field that is containing the delegation target.
*/
private static final String PREFIX = "methodDelegate";
/**
* The name of the field that is containing the delegation target.
*/
private final String fieldName;
/**
* The delegation target.
*/
private final Object delegate;
/**
* Creates a new instrumentation to an instance that is stored in a {@code static} field.
* The field name will be created randomly.
*
* @param delegate The actual delegation target.
*/
public ForStaticField(Object delegate) {
this(delegate, String.format("%s$%d", PREFIX, delegate.hashCode()));
}
/**
* Creates a new instrumentation to an instance that is stored in a {@code static} field.
*
* @param delegate The actual delegation target.
* @param fieldName The name of the field for storing the delegate instance.
*/
public ForStaticField(Object delegate, String fieldName) {
this.delegate = delegate;
this.fieldName = fieldName;
}
@Override
public InstrumentedType prepare(InstrumentedType instrumentedType) {
return instrumentedType.withField(fieldName,
new TypeDescription.ForLoadedType(delegate.getClass()),
Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC | Opcodes.ACC_SYNTHETIC)
.withInitializer(LoadedTypeInitializer.ForStaticField.nonAccessible(fieldName, delegate));
}
@Override
public StackManipulation getPreparingStackAssignment(TypeDescription instrumentedType) {
return FieldAccess.forField(instrumentedType.getDeclaredFields()
.filter((named(fieldName))).getOnly()).getter();
}
@Override
public MethodDelegationBinder.MethodInvoker getMethodInvoker(TypeDescription instrumentedType) {
return new MethodDelegationBinder.MethodInvoker.Virtual(new TypeDescription.ForLoadedType(delegate.getClass()));
}
@Override
public boolean equals(Object other) {
return this == other || !(other == null || getClass() != other.getClass())
&& delegate.equals(((ForStaticField) other).delegate)
&& fieldName.equals(((ForStaticField) other).fieldName);
}
@Override
public int hashCode() {
return 31 * fieldName.hashCode() + delegate.hashCode();
}
@Override
public String toString() {
return "MethodDelegation.InstrumentationDelegate.ForStaticField{" +
"fieldName='" + fieldName + '\'' +
", delegate=" + delegate +
'}';
}
}
/**
* An instrumentation applied on an instance field.
*/
static class ForInstanceField implements InstrumentationDelegate {
/**
* The name of the instance field that is containing the target of the method delegation.
*/
private final String fieldName;
/**
* The type of the method delegation target.
*/
private final TypeDescription fieldType;
/**
* Creates a new instance field instrumentation delegate.
*
* @param fieldType A description of the type that is the target of the instrumentation and thus also the
* field type.
* @param fieldName The name of the field.
*/
public ForInstanceField(TypeDescription fieldType, String fieldName) {
this.fieldType = fieldType;
this.fieldName = fieldName;
}
@Override
public InstrumentedType prepare(InstrumentedType instrumentedType) {
return instrumentedType.withField(fieldName, fieldType, Opcodes.ACC_PUBLIC);
}
@Override
public StackManipulation getPreparingStackAssignment(TypeDescription instrumentedType) {
return new StackManipulation.Compound(MethodVariableAccess.forType(instrumentedType).loadFromIndex(0),
FieldAccess.forField(instrumentedType.getDeclaredFields()
.filter((named(fieldName))).getOnly()).getter());
}
@Override
public MethodDelegationBinder.MethodInvoker getMethodInvoker(TypeDescription instrumentedType) {
return new MethodDelegationBinder.MethodInvoker.Virtual(fieldType);
}
@Override
public boolean equals(Object other) {
return this == other || !(other == null || getClass() != other.getClass())
&& fieldName.equals(((ForInstanceField) other).fieldName)
&& fieldType.equals(((ForInstanceField) other).fieldType);
}
@Override
public int hashCode() {
return 31 * fieldName.hashCode() + fieldType.hashCode();
}
@Override
public String toString() {
return "MethodDelegation.InstrumentationDelegate.ForInstanceField{" +
"fieldName='" + fieldName + '\'' +
", fieldType=" + fieldType +
'}';
}
}
/**
* An instrumentation that creates new instances of a given type.
*/
static class ForConstruction implements InstrumentationDelegate {
/**
* The type that is to be constructed.
*/
private final TypeDescription typeDescription;
/**
* Creates a new constructor instrumentation.
*
* @param typeDescription The type to be constructed.
*/
public ForConstruction(TypeDescription typeDescription) {
this.typeDescription = typeDescription;
}
@Override
public InstrumentedType prepare(InstrumentedType instrumentedType) {
return instrumentedType;
}
@Override
public StackManipulation getPreparingStackAssignment(TypeDescription instrumentedType) {
return new StackManipulation.Compound(
TypeCreation.forType(typeDescription),
Duplication.SINGLE);
}
@Override
public MethodDelegationBinder.MethodInvoker getMethodInvoker(TypeDescription instrumentedType) {
return MethodDelegationBinder.MethodInvoker.Simple.INSTANCE;
}
@Override
public boolean equals(Object other) {
return this == other || !(other == null || getClass() != other.getClass())
&& typeDescription.equals(((ForConstruction) other).typeDescription);
}
@Override
public int hashCode() {
return typeDescription.hashCode();
}
@Override
public String toString() {
return "MethodDelegation.InstrumentationDelegate.ForConstruction{" +
"typeDescription=" + typeDescription +
'}';
}
}
}
/**
* The appender for implementing a {@link com.ui4j.bytebuddy.instrumentation.MethodDelegation}.
*/
protected static class Appender implements ByteCodeAppender {
/**
* The stack manipulation that is responsible for loading a potential target instance onto the stack
* on which the target method is invoked.
*/
private final StackManipulation preparingStackAssignment;
/**
* The instrumentation target of this instrumentation.
*/
private final Target instrumentationTarget;
/**
* The method candidates to consider for delegating the invocation to.
*/
private final Iterable extends MethodDescription> targetMethods;
/**
* The method delegation binder processor which is responsible for implementing the method delegation.
*/
private final MethodDelegationBinder.Processor processor;
/**
* Creates a new appender.
*
* @param preparingStackAssignment The stack manipulation that is responsible for loading a potential target
* instance onto the stack on which the target method is invoked.
* @param instrumentationTarget The instrumentation target of this instrumentation.
* @param targetMethods The method candidates to consider for delegating the invocation to.
* @param processor The method delegation binder processor which is responsible for implementing
* the method delegation.
*/
protected Appender(StackManipulation preparingStackAssignment,
Target instrumentationTarget,
Iterable extends MethodDescription> targetMethods,
MethodDelegationBinder.Processor processor) {
this.preparingStackAssignment = preparingStackAssignment;
this.instrumentationTarget = instrumentationTarget;
this.targetMethods = targetMethods;
this.processor = processor;
}
@Override
public boolean appendsCode() {
return true;
}
@Override
public Size apply(MethodVisitor methodVisitor,
Context instrumentationContext,
MethodDescription instrumentedMethod) {
StackManipulation.Size stackSize = new StackManipulation.Compound(
preparingStackAssignment,
processor.process(instrumentationTarget, instrumentedMethod, targetMethods)
).apply(methodVisitor, instrumentationContext);
return new Size(stackSize.getMaximalSize(), instrumentedMethod.getStackSize());
}
@Override
public boolean equals(Object other) {
if (this == other) return true;
if (other == null || getClass() != other.getClass()) return false;
Appender that = (Appender) other;
return instrumentationTarget.equals(that.instrumentationTarget)
&& preparingStackAssignment.equals(that.preparingStackAssignment)
&& processor.equals(that.processor)
&& targetMethods.equals(that.targetMethods);
}
@Override
public int hashCode() {
int result = preparingStackAssignment.hashCode();
result = 31 * result + instrumentationTarget.hashCode();
result = 31 * result + targetMethods.hashCode();
result = 31 * result + processor.hashCode();
return result;
}
@Override
public String toString() {
return "MethodDelegation.Appender{" +
"preparingStackAssignment=" + preparingStackAssignment +
", instrumentationTarget=" + instrumentationTarget +
", targetMethods=" + targetMethods +
", processor=" + processor +
'}';
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy