All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.fitbur.bytebuddy.implementation.MethodDelegation Maven / Gradle / Ivy

There is a newer version: 1.0.0
Show newest version
package com.fitbur.bytebuddy.implementation;

import com.fitbur.bytebuddy.description.field.FieldDescription;
import com.fitbur.bytebuddy.description.method.MethodDescription;
import com.fitbur.bytebuddy.description.method.MethodList;
import com.fitbur.bytebuddy.description.type.TypeDefinition;
import com.fitbur.bytebuddy.description.type.TypeDescription;
import com.fitbur.bytebuddy.dynamic.scaffold.InstrumentedType;
import com.fitbur.bytebuddy.dynamic.scaffold.MethodGraph;
import com.fitbur.bytebuddy.implementation.bind.MethodDelegationBinder;
import com.fitbur.bytebuddy.implementation.bind.annotation.Argument;
import com.fitbur.bytebuddy.implementation.bind.annotation.TargetMethodAnnotationDrivenBinder;
import com.fitbur.bytebuddy.implementation.bytecode.ByteCodeAppender;
import com.fitbur.bytebuddy.implementation.bytecode.Duplication;
import com.fitbur.bytebuddy.implementation.bytecode.StackManipulation;
import com.fitbur.bytebuddy.implementation.bytecode.TypeCreation;
import com.fitbur.bytebuddy.implementation.bytecode.assign.Assigner;
import com.fitbur.bytebuddy.implementation.bytecode.member.FieldAccess;
import com.fitbur.bytebuddy.implementation.bytecode.member.MethodVariableAccess;
import com.fitbur.bytebuddy.matcher.ElementMatcher;
import com.fitbur.bytebuddy.utility.CompoundList;
import com.fitbur.bytebuddy.jar.asm.MethodVisitor;
import com.fitbur.bytebuddy.jar.asm.Opcodes;

import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.List;

import static com.fitbur.bytebuddy.matcher.ElementMatchers.*;

/**
 * This implementation 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.fitbur.bytebuddy.implementation.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.fitbur.bytebuddy.implementation.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.fitbur.bytebuddy.implementation.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.fitbur.bytebuddy.implementation.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.fitbur.bytebuddy.implementation.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.fitbur.bytebuddy.implementation.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.fitbur.bytebuddy.implementation.bind.annotation.DefaultCall}: * This annotation is similar to the {@link com.fitbur.bytebuddy.implementation.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.fitbur.bytebuddy.implementation.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}, * a {@link java.lang.reflect.Constructor}, a {@code java.lang.reflect.Executable} or a {@link java.lang.Class} instance. * A {@code Method}-typed, {@code Constructor} or {@code Executable} parameter is assigned a reference to the original * method that is instrumented. A {@code Class}-typed parameter is assigned the type of the caller. Furthermore, {@code MethodType} * and {@code MethodHandle} parameters are also supported. When using the annotation on a {@link java.lang.String} type, * the intercepted method's {@code toString} value is injected. The same holds for a parameter of type {@code int} that receives * the modifiers of the instrumented method.
  • *
  • {@link com.fitbur.bytebuddy.implementation.bind.annotation.StubValue}: Assigns the (boxed) default value of the * intercepted method's return type to the parameter. If the return type is {@code void}, {@code null} is assigned.
  • *
  • {@link com.fitbur.bytebuddy.implementation.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.fitbur.bytebuddy.implementation.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.fitbur.bytebuddy.implementation.bind.annotation.Pipe} * annotation's documentation for further information on how this can be done.
  • *
  • {@link com.fitbur.bytebuddy.implementation.bind.annotation.Morph}: The morph annotation is similar to * the {@link com.fitbur.bytebuddy.implementation.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.fitbur.bytebuddy.implementation.bind.annotation.FieldValue}: Allows to access a field's value at the time * of the method invocation. The field's value is directly assigned to the annotated parameter.
  • *
  • {@link com.fitbur.bytebuddy.implementation.bind.annotation.FieldProxy}: 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.fitbur.bytebuddy.implementation.FieldAccessor} in * combination with {@link com.fitbur.bytebuddy.implementation.MethodDelegation#andThen(Implementation)} 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.fitbur.bytebuddy.implementation.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.fitbur.bytebuddy.implementation.bind.annotation.RuntimeType} * annotation can instruct a parameter to be bound by a * {@link com.fitbur.bytebuddy.implementation.bytecode.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: *
    *
  1. {@link com.fitbur.bytebuddy.implementation.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.fitbur.bytebuddy.implementation.bind.annotation.BindingPriority#DEFAULT} * for non-annotated method. A method with a higher priority is considered a better target for delegation.
  2. *
  3. {@link com.fitbur.bytebuddy.implementation.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.
  4. *
  5. {@link com.fitbur.bytebuddy.implementation.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.
  6. *
  7. {@link com.fitbur.bytebuddy.implementation.bind.ArgumentTypeResolver}: * The most specific type resolver will consider all bindings that are using the * {@link com.fitbur.bytebuddy.implementation.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.
  8. *
  9. {@link com.fitbur.bytebuddy.implementation.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.
  10. *
*

* Additionally, if a method is annotated by * {@link com.fitbur.bytebuddy.implementation.bind.annotation.IgnoreForBinding}, * it is never considered as a target for a method delegation. *

*

* Important: For invoking a method on another instance, use the {@link Forwarding} implementation. A method delegation * intends to bind a interceptor class and its resolution algorithm will not necessarily yield a delegation to the intercepted * method. *

* * @see Forwarding */ public class MethodDelegation implements Implementation.Composable { /** * The implementation delegate for this method delegation. */ private final ImplementationDelegate implementationDelegate; /** * A list of {@link com.fitbur.bytebuddy.implementation.bind.annotation.TargetMethodAnnotationDrivenBinder.ParameterBinder}s * to be used by this method delegation. */ private final List> parameterBinders; /** * The {@link com.fitbur.bytebuddy.implementation.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.fitbur.bytebuddy.implementation.bind.MethodDelegationBinder.AmbiguityResolver} * to be used by this method delegation. */ private final MethodDelegationBinder.AmbiguityResolver ambiguityResolver; /** * The {@link com.fitbur.bytebuddy.implementation.bytecode.assign.Assigner} to be used by this method delegation. */ private final Assigner assigner; /** * A method container to query for methods to be considered for delegation. */ private final MethodContainer methodContainer; /** * Creates a new method delegation. * * @param implementationDelegate The implementation 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 methodContainer A method container to query for methods to be considered for delegation. */ protected MethodDelegation(ImplementationDelegate implementationDelegate, List> parameterBinders, TargetMethodAnnotationDrivenBinder.DefaultsProvider defaultsProvider, TargetMethodAnnotationDrivenBinder.TerminationHandler terminationHandler, MethodDelegationBinder.AmbiguityResolver ambiguityResolver, Assigner assigner, MethodContainer methodContainer) { this.implementationDelegate = implementationDelegate; this.parameterBinders = parameterBinders; this.defaultsProvider = defaultsProvider; this.terminationHandler = terminationHandler; this.ambiguityResolver = ambiguityResolver; this.assigner = assigner; this.methodContainer = methodContainer; } /** * Creates an implementation 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 implementation to the given {@code static} methods. */ public static MethodDelegation to(Class type) { return to(new TypeDescription.ForLoadedType(type)); } /** * Creates an implementation 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 implementation to the given {@code static} methods. */ public static MethodDelegation to(TypeDescription typeDescription) { 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(ImplementationDelegate.ForStaticMethod.INSTANCE, TargetMethodAnnotationDrivenBinder.ParameterBinder.DEFAULTS, Argument.NextUnboundAsDefaultsProvider.INSTANCE, TargetMethodAnnotationDrivenBinder.TerminationHandler.Returning.INSTANCE, MethodDelegationBinder.AmbiguityResolver.DEFAULT, Assigner.DEFAULT, MethodContainer.ForExplicitMethods.ofStatic(typeDescription)); } /** * Creates an implementation 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.fitbur.bytebuddy.implementation.MethodDelegation#filter(com.fitbur.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.fitbur.bytebuddy.implementation.LoadedTypeInitializer}. All intercepted method calls are * then delegated to this instance. * @return A method delegation implementation to the given instance methods. */ public static MethodDelegation to(Object delegate) { return to(delegate, MethodGraph.Compiler.DEFAULT); } /** * Creates an implementation 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.fitbur.bytebuddy.implementation.MethodDelegation#filter(com.fitbur.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.fitbur.bytebuddy.implementation.LoadedTypeInitializer}. All intercepted method calls are * then delegated to this instance. * @param methodGraphCompiler The method graph compiler to be used for locating methods to delegate to. * @return A method delegation implementation to the given instance methods. */ public static MethodDelegation to(Object delegate, MethodGraph.Compiler methodGraphCompiler) { return to(delegate, delegate.getClass(), methodGraphCompiler); } /** * Creates an implementation 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.fitbur.bytebuddy.implementation.MethodDelegation#filter(com.fitbur.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.fitbur.bytebuddy.implementation.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 implementation to the given {@code static} methods. */ public static MethodDelegation to(Object delegate, String fieldName) { return to(delegate, fieldName, MethodGraph.Compiler.DEFAULT); } /** * Creates an implementation 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.fitbur.bytebuddy.implementation.MethodDelegation#filter(com.fitbur.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.fitbur.bytebuddy.implementation.LoadedTypeInitializer}. All intercepted method calls are * then delegated to this instance. * @param fieldName The name of the field for storing the delegate instance. * @param methodGraphCompiler The method graph compiler to be used for locating methods to delegate to. * @return A method delegation implementation to the given {@code static} methods. */ public static MethodDelegation to(Object delegate, String fieldName, MethodGraph.Compiler methodGraphCompiler) { return to(delegate, delegate.getClass(), fieldName, methodGraphCompiler); } /** * Creates an implementation 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.fitbur.bytebuddy.implementation.MethodDelegation#filter(com.fitbur.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.fitbur.bytebuddy.implementation.LoadedTypeInitializer}. All intercepted method calls are * then delegated to this instance. * @param type The type as which the delegate is treated for resolving its methods. * @return A method delegation implementation to the given instance methods. */ public static MethodDelegation to(Object delegate, Type type) { return to(delegate, type, MethodGraph.Compiler.DEFAULT); } /** * Creates an implementation 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.fitbur.bytebuddy.implementation.MethodDelegation#filter(com.fitbur.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.fitbur.bytebuddy.implementation.LoadedTypeInitializer}. All intercepted method calls are * then delegated to this instance. * @param type The type as which the delegate is treated for resolving its methods. * @param methodGraphCompiler The method graph compiler to be used for locating methods to delegate to. * @return A method delegation implementation to the given instance methods. */ public static MethodDelegation to(Object delegate, Type type, MethodGraph.Compiler methodGraphCompiler) { return to(delegate, type, String.format("%s$%d", ImplementationDelegate.ForStaticField.PREFIX, Math.abs(delegate.hashCode() % Integer.MAX_VALUE)), methodGraphCompiler); } /** * Creates an implementation 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.fitbur.bytebuddy.implementation.MethodDelegation#filter(com.fitbur.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.fitbur.bytebuddy.implementation.LoadedTypeInitializer}. All intercepted method calls are * then delegated to this instance. * @param type The type as which the delegate is treated for resolving its methods. * @param fieldName The name of the field for storing the delegate instance. * @return A method delegation implementation to the given {@code static} methods. */ public static MethodDelegation to(Object delegate, Type type, String fieldName) { return to(delegate, type, fieldName, MethodGraph.Compiler.DEFAULT); } /** * Creates an implementation 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.fitbur.bytebuddy.implementation.MethodDelegation#filter(com.fitbur.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.fitbur.bytebuddy.implementation.LoadedTypeInitializer}. All intercepted method calls are * then delegated to this instance. * @param type The type as which the delegate is treated for resolving its methods. * @param fieldName The name of the field for storing the delegate instance. * @param methodGraphCompiler The method graph compiler to be used for locating methods to delegate to. * @return A method delegation implementation to the given {@code static} methods. */ public static MethodDelegation to(Object delegate, Type type, String fieldName, MethodGraph.Compiler methodGraphCompiler) { TypeDescription.Generic typeDescription = TypeDefinition.Sort.describe(type); if (!typeDescription.asErasure().isInstance(delegate)) { throw new IllegalArgumentException(delegate + " is not an instance of " + type); } return new MethodDelegation(new ImplementationDelegate.ForStaticField(delegate, fieldName), TargetMethodAnnotationDrivenBinder.ParameterBinder.DEFAULTS, Argument.NextUnboundAsDefaultsProvider.INSTANCE, TargetMethodAnnotationDrivenBinder.TerminationHandler.Returning.INSTANCE, MethodDelegationBinder.AmbiguityResolver.DEFAULT, Assigner.DEFAULT, new MethodContainer.ForVirtualMethods(methodGraphCompiler, typeDescription)); } /** * Creates an implementation 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.fitbur.bytebuddy.implementation.MethodDelegation#filter(com.fitbur.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(type), fieldName); } /** * Creates an implementation 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.fitbur.bytebuddy.implementation.MethodDelegation#filter(com.fitbur.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(typeDescription, fieldName, MethodGraph.Compiler.DEFAULT); } /** * Creates an implementation 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.fitbur.bytebuddy.implementation.MethodDelegation#filter(com.fitbur.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. * @param methodGraphCompiler The method graph compiler to be used for locating methods to delegate to. * @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, MethodGraph.Compiler methodGraphCompiler) { return toInstanceField(new TypeDescription.ForLoadedType(type), fieldName, methodGraphCompiler); } /** * Creates an implementation 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.fitbur.bytebuddy.implementation.MethodDelegation#filter(com.fitbur.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 {@code 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 typeDefinition The type of the delegate and the field. * @param fieldName The name of the field. * @param methodGraphCompiler The method graph compiler to be used for locating methods to delegate to. * @return A method delegation that intercepts method calls by delegating to method calls on the given instance. */ public static MethodDelegation toInstanceField(TypeDefinition typeDefinition, String fieldName, MethodGraph.Compiler methodGraphCompiler) { return new MethodDelegation(new ImplementationDelegate.ForInstanceField(typeDefinition.asGenericType(), fieldName), TargetMethodAnnotationDrivenBinder.ParameterBinder.DEFAULTS, Argument.NextUnboundAsDefaultsProvider.INSTANCE, TargetMethodAnnotationDrivenBinder.TerminationHandler.Returning.INSTANCE, MethodDelegationBinder.AmbiguityResolver.DEFAULT, Assigner.DEFAULT, new MethodContainer.ForVirtualMethods(methodGraphCompiler, typeDefinition.asGenericType())); } /** * Creates an implementation 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 implementation that creates instances of the given type as its result. */ public static MethodDelegation toConstructor(Class type) { return toConstructor(new TypeDescription.ForLoadedType(type)); } /** * Creates an implementation 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 implementation that creates instances of the given type as its result. */ public static MethodDelegation toConstructor(TypeDescription typeDescription) { return new MethodDelegation(new ImplementationDelegate.ForConstruction(typeDescription), TargetMethodAnnotationDrivenBinder.ParameterBinder.DEFAULTS, Argument.NextUnboundAsDefaultsProvider.INSTANCE, TargetMethodAnnotationDrivenBinder.TerminationHandler.Returning.INSTANCE, MethodDelegationBinder.AmbiguityResolver.DEFAULT, Assigner.DEFAULT, MethodContainer.ForExplicitMethods.ofConstructors(typeDescription)); } /** * 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 implementation that makes use of the given parameter binder. */ public MethodDelegation appendParameterBinder(TargetMethodAnnotationDrivenBinder.ParameterBinder parameterBinder) { return new MethodDelegation(implementationDelegate, CompoundList.of(parameterBinders, parameterBinder), defaultsProvider, terminationHandler, ambiguityResolver, assigner, methodContainer); } /** * 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 implementation that makes use of the given parameter binders. */ public MethodDelegation defineParameterBinder(TargetMethodAnnotationDrivenBinder.ParameterBinder... parameterBinder) { return new MethodDelegation(implementationDelegate, Arrays.asList(parameterBinder), defaultsProvider, terminationHandler, ambiguityResolver, assigner, methodContainer); } /** * A provider for annotation instances on values that are not explicitly annotated. * * @param defaultsProvider The defaults provider to use. * @return A method delegation implementation that makes use of the given defaults provider. */ public MethodDelegation withDefaultsProvider(TargetMethodAnnotationDrivenBinder.DefaultsProvider defaultsProvider) { return new MethodDelegation(implementationDelegate, parameterBinders, defaultsProvider, terminationHandler, ambiguityResolver, assigner, methodContainer); } /** * 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 implementation that makes use of the given ambiguity resolver. */ public MethodDelegation appendAmbiguityResolver(MethodDelegationBinder.AmbiguityResolver ambiguityResolver) { return defineAmbiguityResolver(new MethodDelegationBinder.AmbiguityResolver.Chain(this.ambiguityResolver, ambiguityResolver)); } /** * Defines an ambiguity resolver to be used for resolving binding conflicts. * * @param ambiguityResolver The ambiguity resolver to use exclusively. * @return A method delegation implementation that makes use of the given ambiguity resolver. */ public MethodDelegation defineAmbiguityResolver(MethodDelegationBinder.AmbiguityResolver... ambiguityResolver) { return new MethodDelegation(implementationDelegate, parameterBinders, defaultsProvider, terminationHandler, new MethodDelegationBinder.AmbiguityResolver.Chain(ambiguityResolver), assigner, methodContainer); } /** * 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 implementation that makes use of the given designer. */ public MethodDelegation withAssigner(Assigner assigner) { return new MethodDelegation(implementationDelegate, parameterBinders, defaultsProvider, terminationHandler, ambiguityResolver, assigner, methodContainer); } /** * 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 methodMatcher) { return new MethodDelegation(implementationDelegate, parameterBinders, defaultsProvider, terminationHandler, ambiguityResolver, assigner, methodContainer.filter(methodMatcher)); } @Override public Implementation andThen(Implementation implementation) { return new Compound(new MethodDelegation(implementationDelegate, parameterBinders, defaultsProvider, TargetMethodAnnotationDrivenBinder.TerminationHandler.Dropping.INSTANCE, ambiguityResolver, assigner, methodContainer), implementation); } @Override public InstrumentedType prepare(InstrumentedType instrumentedType) { return implementationDelegate.prepare(instrumentedType); } @Override public ByteCodeAppender appender(Target implementationTarget) { return new Appender(implementationDelegate.getPreparingStackAssignment(implementationTarget.getInstrumentedType()), implementationTarget, methodContainer.resolve(implementationTarget.getInstrumentedType()), new MethodDelegationBinder.Processor(new TargetMethodAnnotationDrivenBinder( parameterBinders, defaultsProvider, terminationHandler, assigner, implementationDelegate.getMethodInvoker(implementationTarget.getInstrumentedType()) ), 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) && implementationDelegate.equals(that.implementationDelegate) && methodContainer.equals(that.methodContainer) && parameterBinders.equals(that.parameterBinders); } @Override public int hashCode() { int result = implementationDelegate.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 + methodContainer.hashCode(); return result; } @Override public String toString() { return "MethodDelegation{" + "implementationDelegate=" + implementationDelegate + ", parameterBinders=" + parameterBinders + ", defaultsProvider=" + defaultsProvider + ", terminationHandler=" + terminationHandler + ", ambiguityResolver=" + ambiguityResolver + ", assigner=" + assigner + ", methodContainer=" + methodContainer + '}'; } /** * An implementation delegate is responsible for executing the actual method delegation. */ protected interface ImplementationDelegate { /** * 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 implementation. * * @param instrumentedType A description of the instrumented type to which the implementation 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 implementation is applied. * @return A method invoker responsible for invoking the delegation method. */ MethodDelegationBinder.MethodInvoker getMethodInvoker(TypeDescription instrumentedType); /** * An implementation applied to a static method. */ enum ForStaticMethod implements ImplementationDelegate { /** * The singleton instance. */ INSTANCE; @Override public InstrumentedType prepare(InstrumentedType instrumentedType) { return instrumentedType; } @Override public StackManipulation getPreparingStackAssignment(TypeDescription instrumentedType) { return StackManipulation.Trivial.INSTANCE; } @Override public MethodDelegationBinder.MethodInvoker getMethodInvoker(TypeDescription instrumentedType) { return MethodDelegationBinder.MethodInvoker.Simple.INSTANCE; } @Override public String toString() { return "MethodDelegation.ImplementationDelegate.ForStaticMethod." + name(); } } /** * An implementation applied on a static field. */ class ForStaticField implements ImplementationDelegate { /** * The name prefix for the {@code static} field that is containing the delegation target. */ protected static final String PREFIX = "delegate"; /** * 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 implementation for delegating 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(new FieldDescription.Token(fieldName, Opcodes.ACC_SYNTHETIC | Opcodes.ACC_STATIC | Opcodes.ACC_PUBLIC, new TypeDescription.Generic.OfNonGenericType.ForLoadedType(delegate.getClass()))) .withInitializer(new LoadedTypeInitializer.ForStaticField(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.ImplementationDelegate.ForStaticField{" + "fieldName='" + fieldName + '\'' + ", delegate=" + delegate + '}'; } } /** * An implementation applied on an instance field. */ class ForInstanceField implements ImplementationDelegate { /** * 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.Generic fieldType; /** * Creates a new instance field implementation delegate. * * @param fieldType A description of the type that is the target of the implementation and thus also the * field type. * @param fieldName The name of the field. */ public ForInstanceField(TypeDescription.Generic fieldType, String fieldName) { this.fieldType = fieldType; this.fieldName = fieldName; } @Override public InstrumentedType prepare(InstrumentedType instrumentedType) { return instrumentedType.withField(new FieldDescription.Token(fieldName, Opcodes.ACC_PUBLIC, fieldType)); } @Override public StackManipulation getPreparingStackAssignment(TypeDescription instrumentedType) { return new StackManipulation.Compound(MethodVariableAccess.REFERENCE.loadOffset(0), FieldAccess.forField(instrumentedType.getDeclaredFields().filter((named(fieldName))).getOnly()).getter()); } @Override public MethodDelegationBinder.MethodInvoker getMethodInvoker(TypeDescription instrumentedType) { return new MethodDelegationBinder.MethodInvoker.Virtual(fieldType.asErasure()); } @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.ImplementationDelegate.ForInstanceField{" + "fieldName='" + fieldName + '\'' + ", fieldType=" + fieldType + '}'; } } /** * An implementation that creates new instances of a given type. */ class ForConstruction implements ImplementationDelegate { /** * The type that is to be constructed. */ private final TypeDescription typeDescription; /** * Creates a new constructor implementation. * * @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.of(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.ImplementationDelegate.ForConstruction{" + "typeDescription=" + typeDescription + '}'; } } } /** * A method container collects methods that are considered as a target for delegation. */ protected interface MethodContainer { /** * Appends a filter that is applied to the methods that this container represents. * * @param matcher The matcher that is to be applied for filtering methods. * @return A method container with the supplied filter applied. */ MethodContainer filter(ElementMatcher matcher); /** * Resolves this method container to extract a list of methods to be considered for interception. * * @param instrumentedType The instrumented type. * @return A list of methods to be considered as delegation target. */ MethodList resolve(TypeDescription instrumentedType); /** * A method container for an explicit list of methods. */ class ForExplicitMethods implements MethodContainer { /** * The methods to be considered. */ private final MethodList methodList; /** * Creates a new explicit method container. * * @param methodList The methods to be considered. */ protected ForExplicitMethods(MethodList methodList) { this.methodList = methodList; } /** * Creates a container for all static methods of the given type description. * * @param typeDescription The type description of which all static methods should be considered. * @return An appropriate method container. */ protected static MethodContainer ofStatic(TypeDescription typeDescription) { return new ForExplicitMethods(typeDescription.getDeclaredMethods().filter(isStatic())); } /** * Creates a container for all constructors of the given type description. * * @param typeDescription The type description of which all constructors should be considered. * @return An appropriate method container. */ protected static MethodContainer ofConstructors(TypeDescription typeDescription) { return new ForExplicitMethods(typeDescription.getDeclaredMethods().filter(isConstructor())); } @Override public MethodContainer filter(ElementMatcher matcher) { return new ForExplicitMethods(methodList.filter(matcher)); } @Override public MethodList resolve(TypeDescription instrumentedType) { return methodList.filter(isVisibleTo(instrumentedType)); } @Override public boolean equals(Object other) { return this == other || !(other == null || getClass() != other.getClass()) && methodList.equals(((ForExplicitMethods) other).methodList); } @Override public int hashCode() { return methodList.hashCode(); } @Override public String toString() { return "MethodDelegation.MethodContainer.ForExplicitMethods{" + "methodList=" + methodList + '}'; } } /** * A method container for which all virtual methods of a given type should be considered. */ class ForVirtualMethods implements MethodContainer { /** * The method graph compiler to use. */ private final MethodGraph.Compiler methodGraphCompiler; /** * The target type for which the virtual methods should be extracted. */ private final TypeDescription.Generic targetType; /** * A matcher representing a filter to be applied to the extracted methods. */ private final ElementMatcher matcher; /** * Creates a new method container for virtual method extraction. * * @param methodGraphCompiler The method graph compiler to use. * @param targetType The target type for which the virtual methods should be extracted. */ protected ForVirtualMethods(MethodGraph.Compiler methodGraphCompiler, TypeDescription.Generic targetType) { this(methodGraphCompiler, targetType, any()); } /** * Creates a new method container for virtual method extraction. * * @param methodGraphCompiler The method graph compiler to use. * @param targetType The target type for which the virtual methods should be extracted. * @param matcher A matcher representing a filter to be applied to the extracted methods. */ private ForVirtualMethods(MethodGraph.Compiler methodGraphCompiler, TypeDescription.Generic targetType, ElementMatcher matcher) { this.methodGraphCompiler = methodGraphCompiler; this.targetType = targetType; this.matcher = matcher; } @Override public MethodContainer filter(ElementMatcher matcher) { return new ForVirtualMethods(methodGraphCompiler, targetType, new ElementMatcher.Junction.Conjunction(this.matcher, matcher)); } @Override public MethodList resolve(TypeDescription instrumentedType) { if (!targetType.asErasure().isVisibleTo(instrumentedType)) { throw new IllegalStateException(instrumentedType + " cannot access " + targetType); } return methodGraphCompiler.compile(targetType, instrumentedType).listNodes().asMethodList().filter(matcher); } @Override public boolean equals(Object other) { if (this == other) return true; if (other == null || getClass() != other.getClass()) return false; ForVirtualMethods that = (ForVirtualMethods) other; return methodGraphCompiler.equals(that.methodGraphCompiler) && targetType.equals(that.targetType) && matcher.equals(that.matcher); } @Override public int hashCode() { int result = methodGraphCompiler.hashCode(); result = 31 * result + targetType.hashCode(); result = 31 * result + matcher.hashCode(); return result; } @Override public String toString() { return "MethodDelegation.MethodContainer.ForVirtualMethods{" + "methodGraphCompiler=" + methodGraphCompiler + ", targetType=" + targetType + ", matcher=" + matcher + '}'; } } } /** * The appender for implementing a {@link com.fitbur.bytebuddy.implementation.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 implementation target of this implementation. */ private final Target implementationTarget; /** * The method candidates to consider for delegating the invocation to. */ private final MethodList targetCandidates; /** * 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 implementationTarget The implementation target of this implementation. * @param targetCandidates 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 implementationTarget, MethodList targetCandidates, MethodDelegationBinder.Processor processor) { this.preparingStackAssignment = preparingStackAssignment; this.implementationTarget = implementationTarget; this.targetCandidates = targetCandidates; this.processor = processor; } @Override public Size apply(MethodVisitor methodVisitor, Context implementationContext, MethodDescription instrumentedMethod) { StackManipulation.Size stackSize = new StackManipulation.Compound( preparingStackAssignment, processor.process(implementationTarget, instrumentedMethod, targetCandidates) ).apply(methodVisitor, implementationContext); 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 implementationTarget.equals(that.implementationTarget) && preparingStackAssignment.equals(that.preparingStackAssignment) && processor.equals(that.processor) && targetCandidates.equals(that.targetCandidates); } @Override public int hashCode() { int result = preparingStackAssignment.hashCode(); result = 31 * result + implementationTarget.hashCode(); result = 31 * result + targetCandidates.hashCode(); result = 31 * result + processor.hashCode(); return result; } @Override public String toString() { return "MethodDelegation.Appender{" + "preparingStackAssignment=" + preparingStackAssignment + ", implementationTarget=" + implementationTarget + ", targetCandidates=" + targetCandidates + ", processor=" + processor + '}'; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy