/*
 * Copyright 2014 - Present Rafael Winterhalter
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package net.bytebuddy.implementation;

import net.bytebuddy.build.HashCodeAndEqualsPlugin;
import net.bytebuddy.description.field.FieldDescription;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.method.MethodList;
import net.bytebuddy.description.type.TypeDefinition;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.dynamic.scaffold.FieldLocator;
import net.bytebuddy.dynamic.scaffold.InstrumentedType;
import net.bytebuddy.dynamic.scaffold.MethodGraph;
import net.bytebuddy.implementation.bind.MethodDelegationBinder;
import net.bytebuddy.implementation.bind.annotation.TargetMethodAnnotationDrivenBinder;
import net.bytebuddy.implementation.bytecode.ByteCodeAppender;
import net.bytebuddy.implementation.bytecode.Duplication;
import net.bytebuddy.implementation.bytecode.StackManipulation;
import net.bytebuddy.implementation.bytecode.TypeCreation;
import net.bytebuddy.implementation.bytecode.assign.Assigner;
import net.bytebuddy.implementation.bytecode.member.FieldAccess;
import net.bytebuddy.implementation.bytecode.member.MethodInvocation;
import net.bytebuddy.implementation.bytecode.member.MethodVariableAccess;
import net.bytebuddy.matcher.ElementMatcher;
import net.bytebuddy.utility.CompoundList;
import net.bytebuddy.utility.RandomString;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

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

import static net.bytebuddy.matcher.ElementMatchers.*;

/**
 * This implementation delegates a 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.
 * <p>&nbsp;</p>
 * <b>Without any customization, the method delegation will work as follows:</b>
 * <p>&nbsp;</p>
 * <span style="text-decoration: underline">Binding an instrumented method to a given delegate method</span>
 * <p>&nbsp;</p>
 * 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:
 * <ul>
 * <li>{@link net.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.</li>
 * <li>{@link net.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}.</li>
 * <li>{@link net.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.</li>
 * <li>{@link net.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.</li>
 * <li>{@link net.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.</li>
 * <li>{@link net.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.
 * </li>
 * <li>{@link net.bytebuddy.implementation.bind.annotation.SuperCall}:
 * This annotation is similar to the {@link net.bytebuddy.implementation.bind.annotation.DefaultCall}
 * 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. For using method handles, the {@link net.bytebuddy.implementation.bind.annotation.SuperCallHandle}
 * and {@link net.bytebuddy.implementation.bind.annotation.DefaultCallHandle} annotations can be used.</li>
 * <li>The {@link net.bytebuddy.implementation.bind.annotation.SuperMethod} or
 * {@link net.bytebuddy.implementation.bind.annotation.DefaultMethod} annotations can be used on any parameter type
 * that is assignable from the {@link java.lang.reflect.Method} type. the parameter is bound a method instance that
 * allows for the reflective invocation of a super or default method. Note that this method is not equal to the intercepted
 * method but represents a synthetic accessor method. Using this annotation also causes this accessor to be {@code public}
 * which allows its outside invocation without any access checks by a security manager. For using method handles, the
 * {@link net.bytebuddy.implementation.bind.annotation.SuperCallHandle} and
 * {@link net.bytebuddy.implementation.bind.annotation.DefaultCallHandle} annotations can be used.</li>
 * <li>{@link net.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.</li>
 * <li>{@link net.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.</li>
 * <li>{@link net.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.</li>
 * <li>{@link net.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. <b>This annotation needs to be installed and explicitly
 * registered before it can be used.</b> See the {@link net.bytebuddy.implementation.bind.annotation.Pipe}
 * annotation's documentation for further information on how this can be done.</li>
 * <li>{@link net.bytebuddy.implementation.bind.annotation.Morph}: The morph annotation is similar to
 * the {@link net.bytebuddy.implementation.bind.annotation.SuperCall} annotation but allows to
 * explicitly define and therewith alter the arguments that are handed to the super method. <b>This annotation needs
 * to be installed and explicitly registered before it can be used.</b> See the documentation to the annotation for
 * further information.</li>
 * <li>{@link net.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.</li>
 * <li>{@link net.bytebuddy.implementation.bind.annotation.FieldProxy}: Allows to access fields via getter
 * and setter proxies. <b>This annotation needs to be installed and explicitly registered before it can be used.</b>
 * Note that any field access requires boxing such that a use of {@link net.bytebuddy.implementation.FieldAccessor} in
 * combination with {@link net.bytebuddy.implementation.MethodDelegation#andThen(Implementation)} might be a more
 * performant alternative for implementing field getters and setters.</li>
 * </ul>
 * If a method is not annotated with any of the above methods, it will be treated as if it was annotated
 * {@link net.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)}.
 * <p>&nbsp;</p>
 * In addition, the {@link net.bytebuddy.implementation.bind.annotation.RuntimeType}
 * annotation can instruct a parameter to be bound by a
 * {@link net.bytebuddy.implementation.bytecode.assign.Assigner} with considering the
 * runtime type of the parameter.
 * <p>&nbsp;</p>
 * <span style="text-decoration: underline">Selecting among different methods that can be used for binding a method
 * of the instrumented type</span>
 * <p>&nbsp;</p>
 * 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:
 * <ol>
 * <li>{@link net.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 net.bytebuddy.implementation.bind.annotation.BindingPriority#DEFAULT}
 * for non-annotated method. A method with a higher priority is considered a better target for delegation.</li>
 * <li>{@link net.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.</li>
 * <li>{@link net.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.</li>
 * <li>{@link net.bytebuddy.implementation.bind.ArgumentTypeResolver}:
 * The most specific type resolver will consider all bindings that are using the
 * {@link net.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.</li>
 * <li>{@link net.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.</li>
 * </ol>
 * <p>
 * Additionally, if a method is annotated by
 * {@link net.bytebuddy.implementation.bind.annotation.IgnoreForBinding},
 * it is never considered as a target for a method delegation.
 * </p>
 * <p>
 * <b>Important</b>: For invoking a method on another instance, use the {@link MethodCall} implementation. A method delegation
 * intends to bind a interceptor class and its resolution algorithm will not necessarily yield a delegation to the intercepted
 * method.
 * </p>
 *
 * @see MethodCall
 * @see net.bytebuddy.implementation.bind.annotation.TargetMethodAnnotationDrivenBinder.ParameterBinder.ForFixedValue
 */
@HashCodeAndEqualsPlugin.Enhance
public class MethodDelegation implements Implementation.Composable {

    /**
     * The implementation delegate for this method delegation.
     */
    private final ImplementationDelegate implementationDelegate;

    /**
     * A list of {@link net.bytebuddy.implementation.bind.annotation.TargetMethodAnnotationDrivenBinder.ParameterBinder}s
     * to be used by this method delegation.
     */
    private final List<TargetMethodAnnotationDrivenBinder.ParameterBinder<?>> parameterBinders;

    /**
     * The {@link net.bytebuddy.implementation.bind.MethodDelegationBinder.AmbiguityResolver}
     * to be used by this method delegation.
     */
    private final MethodDelegationBinder.AmbiguityResolver ambiguityResolver;

    /**
     * The termination handler to apply.
     */
    private final TargetMethodAnnotationDrivenBinder.TerminationHandler terminationHandler;

    /**
     * The binding resolver being used to select the relevant method binding.
     */
    private final MethodDelegationBinder.BindingResolver bindingResolver;

    /**
     * The {@link net.bytebuddy.implementation.bytecode.assign.Assigner} to be used by this method delegation.
     */
    private final Assigner assigner;

    /**
     * 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 ambiguityResolver      The ambiguity resolver to use by this method delegator.
     * @param bindingResolver        The binding resolver being used to select the relevant method binding.
     */
    protected MethodDelegation(ImplementationDelegate implementationDelegate,
                               List<TargetMethodAnnotationDrivenBinder.ParameterBinder<?>> parameterBinders,
                               MethodDelegationBinder.AmbiguityResolver ambiguityResolver,
                               MethodDelegationBinder.BindingResolver bindingResolver) {
        this(implementationDelegate,
                parameterBinders,
                ambiguityResolver,
                MethodDelegationBinder.TerminationHandler.Default.RETURNING,
                bindingResolver,
                Assigner.DEFAULT);
    }

    /**
     * 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 ambiguityResolver      The ambiguity resolver to use by this method delegator.
     * @param terminationHandler     The termination handler to apply.
     * @param bindingResolver        The binding resolver being used to select the relevant method binding.
     * @param assigner               The assigner to be supplied by this method delegator.
     */
    private MethodDelegation(ImplementationDelegate implementationDelegate,
                             List<TargetMethodAnnotationDrivenBinder.ParameterBinder<?>> parameterBinders,
                             MethodDelegationBinder.AmbiguityResolver ambiguityResolver,
                             TargetMethodAnnotationDrivenBinder.TerminationHandler terminationHandler,
                             MethodDelegationBinder.BindingResolver bindingResolver,
                             Assigner assigner) {
        this.implementationDelegate = implementationDelegate;
        this.parameterBinders = parameterBinders;
        this.terminationHandler = terminationHandler;
        this.ambiguityResolver = ambiguityResolver;
        this.bindingResolver = bindingResolver;
        this.assigner = assigner;
    }

    /**
     * Delegates any intercepted method to invoke a {@code static} method that is declared by the supplied type. To be considered
     * a valid delegation target, the target method must be visible and accessible to the instrumented type. This is the case if
     * the target type is either public or in the same package as the instrumented type and if the target method is either public
     * or non-private and in the same package as the instrumented type. Private methods can only be used as a delegation target if
     * the interception is targeting the instrumented type.
     *
     * @param type The target type for the delegation.
     * @return A method delegation that redirects method calls to a static method of the supplied type.
     */
    public static MethodDelegation to(Class<?> type) {
        return withDefaultConfiguration().to(type);
    }

    /**
     * Delegates any intercepted method to invoke a {@code static} method that is declared by the supplied type. To be considered
     * a valid delegation target, the target method must be visible and accessible to the instrumented type. This is the case if
     * the target type is either public or in the same package as the instrumented type and if the target method is either public
     * or non-private and in the same package as the instrumented type. Private methods can only be used as a delegation target if
     * the interception is targeting the instrumented type.
     *
     * @param typeDescription The target type for the delegation.
     * @return A method delegation that redirects method calls to a static method of the supplied type.
     */
    public static MethodDelegation to(TypeDescription typeDescription) {
        return withDefaultConfiguration().to(typeDescription);
    }

    /**
     * Delegates any intercepted method to invoke a non-{@code static} method that is declared by the supplied type's instance or any
     * of its super types. To be considered a valid delegation target, a method must be visible and accessible to the instrumented type.
     * This is the case if the method's declaring type is either public or in the same package as the instrumented type and if the method
     * is either public or non-private and in the same package as the instrumented type. Private methods can only be used as
     * a delegation target if the delegation is targeting the instrumented type.
     *
     * @param target The target instance for the delegation.
     * @return A method delegation that redirects method calls to a static method of the supplied type.
     */
    public static MethodDelegation to(Object target) {
        return withDefaultConfiguration().to(target);
    }

    /**
     * Delegates any intercepted method to invoke a non-{@code static} method that is declared by the supplied type's instance or any
     * of its super types. To be considered a valid delegation target, a method must be visible and accessible to the instrumented type.
     * This is the case if the method's declaring type is either public or in the same package as the instrumented type and if the method
     * is either public or non-private and in the same package as the instrumented type. Private methods can only be used as
     * a delegation target if the delegation is targeting the instrumented type.
     *
     * @param target              The target instance for the delegation.
     * @param methodGraphCompiler The method graph compiler to use.
     * @return A method delegation that redirects method calls to a static method of the supplied type.
     */
    public static MethodDelegation to(Object target, MethodGraph.Compiler methodGraphCompiler) {
        return withDefaultConfiguration().to(target, methodGraphCompiler);
    }

    /**
     * Delegates any intercepted method to invoke a non-{@code static} method that is declared by the supplied type's instance or any
     * of its super types. To be considered a valid delegation target, a method must be visible and accessible to the instrumented type.
     * This is the case if the method's declaring type is either public or in the same package as the instrumented type and if the method
     * is either public or non-private and in the same package as the instrumented type. Private methods can only be used as
     * a delegation target if the delegation is targeting the instrumented type.
     *
     * @param target    The target instance for the delegation.
     * @param fieldName The name of the field that is holding the {@code target} instance.
     * @return A method delegation that redirects method calls to a static method of the supplied type.
     */
    public static MethodDelegation to(Object target, String fieldName) {
        return withDefaultConfiguration().to(target, fieldName);
    }

    /**
     * Delegates any intercepted method to invoke a non-{@code static} method that is declared by the supplied type's instance or any
     * of its super types. To be considered a valid delegation target, a method must be visible and accessible to the instrumented type.
     * This is the case if the method's declaring type is either public or in the same package as the instrumented type and if the method
     * is either public or non-private and in the same package as the instrumented type. Private methods can only be used as
     * a delegation target if the delegation is targeting the instrumented type.
     *
     * @param target              The target instance for the delegation.
     * @param fieldName           The name of the field that is holding the {@code target} instance.
     * @param methodGraphCompiler The method graph compiler to use.
     * @return A method delegation that redirects method calls to a static method of the supplied type.
     */
    public static MethodDelegation to(Object target, String fieldName, MethodGraph.Compiler methodGraphCompiler) {
        return withDefaultConfiguration().to(target, fieldName, methodGraphCompiler);
    }

    /**
     * Delegates any intercepted method to invoke a non-{@code static} method that is declared by the supplied type's instance or any
     * of its super types. To be considered a valid delegation target, a method must be visible and accessible to the instrumented type.
     * This is the case if the method's declaring type is either public or in the same package as the instrumented type and if the method
     * is either public or non-private and in the same package as the instrumented type. Private methods can only be used as
     * a delegation target if the delegation is targeting the instrumented type.
     *
     * @param target The target instance for the delegation.
     * @param type   The most specific type of which {@code target} should be considered. Must be a super type of the target's actual type.
     * @return A method delegation that redirects method calls to a static method of the supplied type.
     */
    public static MethodDelegation to(Object target, Type type) {
        return withDefaultConfiguration().to(target, type);
    }

    /**
     * Delegates any intercepted method to invoke a non-{@code static} method that is declared by the supplied type's instance or any
     * of its super types. To be considered a valid delegation target, a method must be visible and accessible to the instrumented type.
     * This is the case if the method's declaring type is either public or in the same package as the instrumented type and if the method
     * is either public or non-private and in the same package as the instrumented type. Private methods can only be used as
     * a delegation target if the delegation is targeting the instrumented type.
     *
     * @param target              The target instance for the delegation.
     * @param type                The most specific type of which {@code target} should be considered. Must be a super type of the target's actual type.
     * @param methodGraphCompiler The method graph compiler to use.
     * @return A method delegation that redirects method calls to a static method of the supplied type.
     */
    public static MethodDelegation to(Object target, Type type, MethodGraph.Compiler methodGraphCompiler) {
        return withDefaultConfiguration().to(target, type, methodGraphCompiler);
    }

    /**
     * Delegates any intercepted method to invoke a non-{@code static} method that is declared by the supplied type's instance or any
     * of its super types. To be considered a valid delegation target, a method must be visible and accessible to the instrumented type.
     * This is the case if the method's declaring type is either public or in the same package as the instrumented type and if the method
     * is either public or non-private and in the same package as the instrumented type. Private methods can only be used as
     * a delegation target if the delegation is targeting the instrumented type.
     *
     * @param target    The target instance for the delegation.
     * @param type      The most specific type of which {@code target} should be considered. Must be a super type of the target's actual type.
     * @param fieldName The name of the field that is holding the {@code target} instance.
     * @return A method delegation that redirects method calls to a static method of the supplied type.
     */
    public static MethodDelegation to(Object target, Type type, String fieldName) {
        return withDefaultConfiguration().to(target, type, fieldName);
    }

    /**
     * Delegates any intercepted method to invoke a non-{@code static} method that is declared by the supplied type's instance or any
     * of its super types. To be considered a valid delegation target, a method must be visible and accessible to the instrumented type.
     * This is the case if the method's declaring type is either public or in the same package as the instrumented type and if the method
     * is either public or non-private and in the same package as the instrumented type. Private methods can only be used as
     * a delegation target if the delegation is targeting the instrumented type.
     *
     * @param target              The target instance for the delegation.
     * @param type                The most specific type of which {@code target} should be considered. Must be a super type of the target's actual type.
     * @param fieldName           The name of the field that is holding the {@code target} instance.
     * @param methodGraphCompiler The method graph compiler to use.
     * @return A method delegation that redirects method calls to a static method of the supplied type.
     */
    public static MethodDelegation to(Object target, Type type, String fieldName, MethodGraph.Compiler methodGraphCompiler) {
        return withDefaultConfiguration().to(target, type, fieldName, methodGraphCompiler);
    }

    /**
     * Delegates any intercepted method to invoke a non-{@code static} method that is declared by the supplied type's instance or any
     * of its super types. To be considered a valid delegation target, a method must be visible and accessible to the instrumented type.
     * This is the case if the method's declaring type is either public or in the same package as the instrumented type and if the method
     * is either public or non-private and in the same package as the instrumented type. Private methods can only be used as
     * a delegation target if the delegation is targeting the instrumented type.
     *
     * @param target         The target instance for the delegation.
     * @param typeDefinition The most specific type of which {@code target} should be considered. Must be a super type of the target's actual type.
     * @return A method delegation that redirects method calls to a static method of the supplied type.
     */
    public static MethodDelegation to(Object target, TypeDefinition typeDefinition) {
        return withDefaultConfiguration().to(target, typeDefinition);
    }

    /**
     * Delegates any intercepted method to invoke a non-{@code static} method that is declared by the supplied type's instance or any
     * of its super types. To be considered a valid delegation target, a method must be visible and accessible to the instrumented type.
     * This is the case if the method's declaring type is either public or in the same package as the instrumented type and if the method
     * is either public or non-private and in the same package as the instrumented type. Private methods can only be used as
     * a delegation target if the delegation is targeting the instrumented type.
     *
     * @param target              The target instance for the delegation.
     * @param typeDefinition      The most specific type of which {@code target} should be considered. Must be a super type of the target's actual type.
     * @param methodGraphCompiler The method graph compiler to use.
     * @return A method delegation that redirects method calls to a static method of the supplied type.
     */
    public static MethodDelegation to(Object target, TypeDefinition typeDefinition, MethodGraph.Compiler methodGraphCompiler) {
        return withDefaultConfiguration().to(target, typeDefinition, methodGraphCompiler);
    }

    /**
     * Delegates any intercepted method to invoke a non-{@code static} method that is declared by the supplied type's instance or any
     * of its super types. To be considered a valid delegation target, a method must be visible and accessible to the instrumented type.
     * This is the case if the method's declaring type is either public or in the same package as the instrumented type and if the method
     * is either public or non-private and in the same package as the instrumented type. Private methods can only be used as
     * a delegation target if the delegation is targeting the instrumented type.
     *
     * @param target         The target instance for the delegation.
     * @param typeDefinition The most specific type of which {@code target} should be considered. Must be a super type of the target's actual type.
     * @param fieldName      The name of the field that is holding the {@code target} instance.
     * @return A method delegation that redirects method calls to a static method of the supplied type.
     */
    public static MethodDelegation to(Object target, TypeDefinition typeDefinition, String fieldName) {
        return withDefaultConfiguration().to(target, typeDefinition, fieldName);
    }

    /**
     * Delegates any intercepted method to invoke a non-{@code static} method that is declared by the supplied type's instance or any
     * of its super types. To be considered a valid delegation target, a method must be visible and accessible to the instrumented type.
     * This is the case if the method's declaring type is either public or in the same package as the instrumented type and if the method
     * is either public or non-private and in the same package as the instrumented type. Private methods can only be used as
     * a delegation target if the delegation is targeting the instrumented type.
     *
     * @param target              The target instance for the delegation.
     * @param typeDefinition      The most specific type of which {@code target} should be considered. Must be a super type of the target's actual type.
     * @param fieldName           The name of the field that is holding the {@code target} instance.
     * @param methodGraphCompiler The method graph compiler to use.
     * @return A method delegation that redirects method calls to a static method of the supplied type.
     */
    public static MethodDelegation to(Object target, TypeDefinition typeDefinition, String fieldName, MethodGraph.Compiler methodGraphCompiler) {
        return withDefaultConfiguration().to(target, typeDefinition, fieldName, methodGraphCompiler);
    }

    /**
     * Delegates any intercepted method to invoke a constructor of the supplied type. To be considered a valid delegation target,
     * a constructor must be visible and accessible to the instrumented type. This is the case if the constructor's declaring type is
     * either public or in the same package as the instrumented type and if the constructor is either public or non-private and in
     * the same package as the instrumented type. Private constructors can only be used as a delegation target if the delegation is
     * targeting the instrumented type.
     *
     * @param type The type to construct.
     * @return A delegation that redirects method calls to a constructor of the supplied type.
     */
    public static MethodDelegation toConstructor(Class<?> type) {
        return withDefaultConfiguration().toConstructor(type);
    }

    /**
     * Delegates any intercepted method to invoke a constructor of the supplied type. To be considered a valid delegation target,
     * a constructor must be visible and accessible to the instrumented type. This is the case if the constructor's declaring type is
     * either public or in the same package as the instrumented type and if the constructor is either public or non-private and in
     * the same package as the instrumented type. Private constructors can only be used as a delegation target if the delegation is
     * targeting the instrumented type.
     *
     * @param typeDescription The type to construct.
     * @return A delegation that redirects method calls to a constructor of the supplied type.
     */
    public static MethodDelegation toConstructor(TypeDescription typeDescription) {
        return withDefaultConfiguration().toConstructor(typeDescription);
    }

    /**
     * Delegates any intercepted method to invoke a non-{@code static} method on the instance of the supplied field. To be
     * considered a valid delegation target, a method must be visible and accessible to the instrumented type. This is the
     * case if the method's declaring type is either public or in the same package as the instrumented type and if the method
     * is either public or non-private and in the same package as the instrumented type. Private methods can only be used as
     * a delegation target if the delegation is targeting the instrumented type.
     *
     * @param name The field's name.
     * @return A delegation that redirects invocations to a method of the specified field's instance.
     */
    public static MethodDelegation toField(String name) {
        return withDefaultConfiguration().toField(name);
    }

    /**
     * Delegates any intercepted method to invoke a non-{@code static} method on the instance of the supplied field. To be
     * considered a valid delegation target, a method must be visible and accessible to the instrumented type. This is the
     * case if the method's declaring type is either public or in the same package as the instrumented type and if the method
     * is either public or non-private and in the same package as the instrumented type. Private methods can only be used as
     * a delegation target if the delegation is targeting the instrumented type.
     *
     * @param name                The field's name.
     * @param fieldLocatorFactory The field locator factory to use.
     * @return A delegation that redirects invocations to a method of the specified field's instance.
     */
    public static MethodDelegation toField(String name, FieldLocator.Factory fieldLocatorFactory) {
        return withDefaultConfiguration().toField(name, fieldLocatorFactory);
    }

    /**
     * Delegates any intercepted method to invoke a non-{@code static} method on the instance of the supplied field. To be
     * considered a valid delegation target, a method must be visible and accessible to the instrumented type. This is the
     * case if the method's declaring type is either public or in the same package as the instrumented type and if the method
     * is either public or non-private and in the same package as the instrumented type. Private methods can only be used as
     * a delegation target if the delegation is targeting the instrumented type.
     *
     * @param name                The field's name.
     * @param methodGraphCompiler The method graph compiler to use.
     * @return A delegation that redirects invocations to a method of the specified field's instance.
     */
    public static MethodDelegation toField(String name, MethodGraph.Compiler methodGraphCompiler) {
        return withDefaultConfiguration().toField(name, methodGraphCompiler);
    }

    /**
     * Delegates any intercepted method to invoke a non-{@code static} method on the instance of the supplied field. To be
     * considered a valid delegation target, a method must be visible and accessible to the instrumented type. This is the
     * case if the method's declaring type is either public or in the same package as the instrumented type and if the method
     * is either public or non-private and in the same package as the instrumented type. Private methods can only be used as
     * a delegation target if the delegation is targeting the instrumented type.
     *
     * @param name                The field's name.
     * @param fieldLocatorFactory The field locator factory to use.
     * @param methodGraphCompiler The method graph compiler to use.
     * @return A delegation that redirects invocations to a method of the specified field's instance.
     */
    public static MethodDelegation toField(String name, FieldLocator.Factory fieldLocatorFactory, MethodGraph.Compiler methodGraphCompiler) {
        return withDefaultConfiguration().toField(name, fieldLocatorFactory, methodGraphCompiler);
    }

    /**
     * Delegates any intercepted method to invoke a method on an instance that is returned by a parameterless method of the
     * given name. To be considered a valid delegation target, a method must be visible and accessible to the instrumented type.
     * This is the case if the method's declaring type is either public or in the same package as the instrumented type and if
     * the method is either public or non-private and in the same package as the instrumented type. Private methods can only
     * be used as a delegation target if the delegation is targeting the instrumented type.
     *
     * @param name The name of the method that returns the delegation target.
     * @return A delegation that redirects invocations to the return value of a method that is declared by the instrumented type.
     */
    public static MethodDelegation toMethodReturnOf(String name) {
        return withDefaultConfiguration().toMethodReturnOf(name);
    }

    /**
     * Delegates any intercepted method to invoke a method on an instance that is returned by a parameterless method of the
     * given name. To be considered a valid delegation target, a method must be visible and accessible to the instrumented type.
     * This is the case if the method's declaring type is either public or in the same package as the instrumented type and if
     * the method is either public or non-private and in the same package as the instrumented type. Private methods can only
     * be used as a delegation target if the delegation is targeting the instrumented type.
     *
     * @param name                The name of the method that returns the delegation target.
     * @param methodGraphCompiler The method graph compiler to use.
     * @return A delegation that redirects invocations to the return value of a method that is declared by the instrumented type.
     */
    public static MethodDelegation toMethodReturnOf(String name, MethodGraph.Compiler methodGraphCompiler) {
        return withDefaultConfiguration().toMethodReturnOf(name, methodGraphCompiler);
    }

    /**
     * Creates a configuration builder for a method delegation that is pre-configured with the ambiguity resolvers defined by
     * {@link net.bytebuddy.implementation.bind.MethodDelegationBinder.AmbiguityResolver#DEFAULT} and the parameter binders
     * defined by {@link net.bytebuddy.implementation.bind.annotation.TargetMethodAnnotationDrivenBinder.ParameterBinder#DEFAULTS}.
     *
     * @return A method delegation configuration with pre-configuration.
     */
    public static WithCustomProperties withDefaultConfiguration() {
        return new WithCustomProperties(MethodDelegationBinder.AmbiguityResolver.DEFAULT, TargetMethodAnnotationDrivenBinder.ParameterBinder.DEFAULTS);
    }

    /**
     * Creates a configuration builder for a method delegation that does not apply any pre-configured
     * {@link net.bytebuddy.implementation.bind.MethodDelegationBinder.AmbiguityResolver}s or
     * {@link net.bytebuddy.implementation.bind.annotation.TargetMethodAnnotationDrivenBinder.ParameterBinder}s.
     *
     * @return A method delegation configuration without any pre-configuration.
     */
    public static WithCustomProperties withEmptyConfiguration() {
        return new WithCustomProperties(MethodDelegationBinder.AmbiguityResolver.NoOp.INSTANCE, Collections.<TargetMethodAnnotationDrivenBinder.ParameterBinder<?>>emptyList());
    }

    /**
     * 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 Implementation.Composable withAssigner(Assigner assigner) {
        return new MethodDelegation(implementationDelegate,
                parameterBinders,
                ambiguityResolver,
                terminationHandler,
                bindingResolver,
                assigner);
    }

    /**
     * {@inheritDoc}
     */
    public Implementation andThen(Implementation implementation) {
        return new Compound(new MethodDelegation(implementationDelegate,
                parameterBinders,
                ambiguityResolver,
                MethodDelegationBinder.TerminationHandler.Default.DROPPING,
                bindingResolver,
                assigner), implementation);
    }

    /**
     * {@inheritDoc}
     */
    public Composable andThen(Composable implementation) {
        return new Compound.Composable(new MethodDelegation(implementationDelegate,
                parameterBinders,
                ambiguityResolver,
                MethodDelegationBinder.TerminationHandler.Default.DROPPING,
                bindingResolver,
                assigner), implementation);
    }

    /**
     * {@inheritDoc}
     */
    public InstrumentedType prepare(InstrumentedType instrumentedType) {
        return implementationDelegate.prepare(instrumentedType);
    }

    /**
     * {@inheritDoc}
     */
    public ByteCodeAppender appender(Target implementationTarget) {
        ImplementationDelegate.Compiled compiled = implementationDelegate.compile(implementationTarget.getInstrumentedType());
        return new Appender(implementationTarget,
                new MethodDelegationBinder.Processor(compiled.getRecords(), ambiguityResolver, bindingResolver),
                terminationHandler,
                assigner,
                compiled);
    }

    /**
     * An implementation delegate is responsible for executing the actual method delegation and for resolving the target methods.
     */
    protected interface ImplementationDelegate extends InstrumentedType.Prepareable {

        /**
         * A name prefix for fields.
         */
        String FIELD_NAME_PREFIX = "delegate";

        /**
         * Compiles this implementation delegate.
         *
         * @param instrumentedType The instrumented type.
         * @return A compiled implementation delegate.
         */
        Compiled compile(TypeDescription instrumentedType);

        /**
         * A compiled implementation delegate.
         */
        interface Compiled {

            /**
             * Resolves a stack manipulation that prepares the delegation invocation.
             *
             * @param instrumentedMethod The instrumented method.
             * @return A stack manipulation that is applied prior to loading arguments and executing the method call.
             */
            StackManipulation prepare(MethodDescription instrumentedMethod);

            /**
             * Resolves an invoker to use for invoking the delegation target.
             *
             * @return The method invoker to use.
             */
            MethodDelegationBinder.MethodInvoker invoke();

            /**
             * Returns a list of binding records to consider for delegation.
             *
             * @return A list of delegation binder records to consider.
             */
            List<MethodDelegationBinder.Record> getRecords();

            /**
             * A compiled implementation delegate for invoking a static method.
             */
            @HashCodeAndEqualsPlugin.Enhance
            class ForStaticCall implements Compiled {

                /**
                 * The list of records to consider.
                 */
                private final List<MethodDelegationBinder.Record> records;

                /**
                 * Creates a new compiled implementation delegate for a static method call.
                 *
                 * @param records The list of records to consider.
                 */
                protected ForStaticCall(List<MethodDelegationBinder.Record> records) {
                    this.records = records;
                }

                /**
                 * {@inheritDoc}
                 */
                public StackManipulation prepare(MethodDescription instrumentedMethod) {
                    return StackManipulation.Trivial.INSTANCE;
                }

                /**
                 * {@inheritDoc}
                 */
                public MethodDelegationBinder.MethodInvoker invoke() {
                    return MethodDelegationBinder.MethodInvoker.Simple.INSTANCE;
                }

                /**
                 * {@inheritDoc}
                 */
                public List<MethodDelegationBinder.Record> getRecords() {
                    return records;
                }
            }

            /**
             * A compiled implementation delegate that invokes methods on a field.
             */
            @HashCodeAndEqualsPlugin.Enhance
            class ForField implements Compiled {

                /**
                 * The field to delegate to.
                 */
                private final FieldDescription fieldDescription;

                /**
                 * The records to consider for delegation.
                 */
                private final List<MethodDelegationBinder.Record> records;

                /**
                 * Creates a new compiled implementation delegate for a field delegation.
                 *
                 * @param fieldDescription The field to delegate to.
                 * @param records          The records to consider for delegation.
                 */
                protected ForField(FieldDescription fieldDescription, List<MethodDelegationBinder.Record> records) {
                    this.fieldDescription = fieldDescription;
                    this.records = records;
                }

                /**
                 * {@inheritDoc}
                 */
                public StackManipulation prepare(MethodDescription instrumentedMethod) {
                    if (instrumentedMethod.isStatic() && !fieldDescription.isStatic()) {
                        throw new IllegalStateException("Cannot read " + fieldDescription + " from " + instrumentedMethod);
                    }
                    return new StackManipulation.Compound(fieldDescription.isStatic()
                            ? StackManipulation.Trivial.INSTANCE
                            : MethodVariableAccess.loadThis(), FieldAccess.forField(fieldDescription).read());
                }

                /**
                 * {@inheritDoc}
                 */
                public MethodDelegationBinder.MethodInvoker invoke() {
                    return new MethodDelegationBinder.MethodInvoker.Virtual(fieldDescription.getType().asErasure());
                }

                /**
                 * {@inheritDoc}
                 */
                public List<MethodDelegationBinder.Record> getRecords() {
                    return records;
                }
            }

            /**
             * A compiled implementation delegate that invokes a method on an instance that is returned by another method.
             */
            @HashCodeAndEqualsPlugin.Enhance
            class ForMethodReturn implements Compiled {

                /**
                 * The method to call for result.
                 */
                private final MethodDescription methodDescription;

                /**
                 * The records to consider for delegation.
                 */
                private final List<MethodDelegationBinder.Record> records;

                /**
                 * Creates a new compiled implementation delegate for a field delegation.
                 *
                 * @param methodDescription The method to call for result.
                 * @param records           The records to consider for delegation.
                 */
                protected ForMethodReturn(MethodDescription methodDescription, List<MethodDelegationBinder.Record> records) {
                    this.methodDescription = methodDescription;
                    this.records = records;
                }

                /**
                 * {@inheritDoc}
                 */
                public StackManipulation prepare(MethodDescription instrumentedMethod) {
                    if (instrumentedMethod.isStatic() && !methodDescription.isStatic()) {
                        throw new IllegalStateException("Cannot invoke " + methodDescription + " from " + instrumentedMethod);
                    }
                    return new StackManipulation.Compound(methodDescription.isStatic()
                            ? StackManipulation.Trivial.INSTANCE
                            : MethodVariableAccess.loadThis(), MethodInvocation.invoke(methodDescription));
                }

                /**
                 * {@inheritDoc}
                 */
                public MethodDelegationBinder.MethodInvoker invoke() {
                    return new MethodDelegationBinder.MethodInvoker.Virtual(methodDescription.getReturnType().asErasure());
                }

                /**
                 * {@inheritDoc}
                 */
                public List<MethodDelegationBinder.Record> getRecords() {
                    return records;
                }
            }

            /**
             * A compiled implementation delegate for a constructor delegation.
             */
            @HashCodeAndEqualsPlugin.Enhance
            class ForConstruction implements Compiled {

                /**
                 * The type to be constructed.
                 */
                private final TypeDescription typeDescription;

                /**
                 * The records to consider for delegation.
                 */
                private final List<MethodDelegationBinder.Record> records;

                /**
                 * Creates a new compiled implementation delegate for a constructor delegation.
                 *
                 * @param typeDescription The type to be constructed.
                 * @param records         The records to consider for delegation.
                 */
                protected ForConstruction(TypeDescription typeDescription, List<MethodDelegationBinder.Record> records) {
                    this.typeDescription = typeDescription;
                    this.records = records;
                }

                /**
                 * {@inheritDoc}
                 */
                public StackManipulation prepare(MethodDescription instrumentedMethod) {
                    return new StackManipulation.Compound(TypeCreation.of(typeDescription), Duplication.SINGLE);
                }

                /**
                 * {@inheritDoc}
                 */
                public MethodDelegationBinder.MethodInvoker invoke() {
                    return MethodDelegationBinder.MethodInvoker.Simple.INSTANCE;
                }

                /**
                 * {@inheritDoc}
                 */
                public List<MethodDelegationBinder.Record> getRecords() {
                    return records;
                }
            }
        }

        /**
         * An implementation delegate for a static method delegation.
         */
        @HashCodeAndEqualsPlugin.Enhance
        class ForStaticMethod implements ImplementationDelegate {

            /**
             * The precompiled records.
             */
            private final List<MethodDelegationBinder.Record> records;

            /**
             * Creates a new implementation delegate for a static method delegation.
             *
             * @param records The precompiled record.
             */
            protected ForStaticMethod(List<MethodDelegationBinder.Record> records) {
                this.records = records;
            }

            /**
             * Precompiles a static method delegation for a given list of methods.
             *
             * @param methods                The methods to consider.
             * @param methodDelegationBinder The method delegation binder to use.
             * @return An appropriate implementation delegate.
             */
            protected static ImplementationDelegate of(MethodList<?> methods, MethodDelegationBinder methodDelegationBinder) {
                List<MethodDelegationBinder.Record> records = new ArrayList<MethodDelegationBinder.Record>(methods.size());
                for (MethodDescription methodDescription : methods) {
                    records.add(methodDelegationBinder.compile(methodDescription));
                }
                return new ForStaticMethod(records);
            }

            /**
             * {@inheritDoc}
             */
            public InstrumentedType prepare(InstrumentedType instrumentedType) {
                return instrumentedType;
            }

            /**
             * {@inheritDoc}
             */
            public ImplementationDelegate.Compiled compile(TypeDescription instrumentedType) {
                return new Compiled.ForStaticCall(records);
            }
        }

        /**
         * An implementation delegate for invoking methods on a field that is declared by the instrumented type or a super type.
         */
        @HashCodeAndEqualsPlugin.Enhance
        abstract class ForField implements ImplementationDelegate {

            /**
             * The name of the field that is target of the delegation.
             */
            protected final String fieldName;

            /**
             * The method graph compiler to use.
             */
            protected final MethodGraph.Compiler methodGraphCompiler;

            /**
             * The parameter binders to use.
             */
            protected final List<? extends TargetMethodAnnotationDrivenBinder.ParameterBinder<?>> parameterBinders;

            /**
             * The matcher to use for filtering methods.
             */
            protected final ElementMatcher<? super MethodDescription> matcher;

            /**
             * Creates a new implementation delegate for a field delegation.
             *
             * @param fieldName           The name of the field that is target of the delegation.
             * @param methodGraphCompiler The method graph compiler to use.
             * @param parameterBinders    The parameter binders to use.
             * @param matcher             The matcher to use for filtering methods.
             */
            protected ForField(String fieldName,
                               MethodGraph.Compiler methodGraphCompiler,
                               List<? extends TargetMethodAnnotationDrivenBinder.ParameterBinder<?>> parameterBinders,
                               ElementMatcher<? super MethodDescription> matcher) {
                this.fieldName = fieldName;
                this.methodGraphCompiler = methodGraphCompiler;
                this.parameterBinders = parameterBinders;
                this.matcher = matcher;
            }

            /**
             * {@inheritDoc}
             */
            public Compiled compile(TypeDescription instrumentedType) {
                FieldDescription fieldDescription = resolve(instrumentedType);
                if (!fieldDescription.getType().asErasure().isVisibleTo(instrumentedType)) {
                    throw new IllegalStateException(fieldDescription + " is not visible to " + instrumentedType);
                } else {
                    MethodList<?> candidates = methodGraphCompiler.compile(fieldDescription.getType(), instrumentedType)
                            .listNodes()
                            .asMethodList()
                            .filter(matcher);
                    List<MethodDelegationBinder.Record> records = new ArrayList<MethodDelegationBinder.Record>(candidates.size());
                    MethodDelegationBinder methodDelegationBinder = TargetMethodAnnotationDrivenBinder.of(parameterBinders);
                    for (MethodDescription candidate : candidates) {
                        records.add(methodDelegationBinder.compile(candidate));
                    }
                    return new Compiled.ForField(fieldDescription, records);
                }
            }

            /**
             * Resolves the field to which is delegated.
             *
             * @param instrumentedType The instrumented type.
             * @return The field that is the delegation target.
             */
            protected abstract FieldDescription resolve(TypeDescription instrumentedType);

            /**
             * An implementation target for a static field that is declared by the instrumented type and that is assigned an instance.
             */
            @HashCodeAndEqualsPlugin.Enhance
            protected static class WithInstance extends ForField {

                /**
                 * The target instance.
                 */
                private final Object target;

                /**
                 * The field's type.
                 */
                private final TypeDescription.Generic fieldType;

                /**
                 * Creates a new implementation delegate for invoking methods on a supplied instance.
                 *
                 * @param fieldName           The name of the field that is target of the delegation.
                 * @param methodGraphCompiler The method graph compiler to use.
                 * @param parameterBinders    The parameter binders to use.
                 * @param matcher             The matcher to use for filtering methods.
                 * @param target              The target instance.
                 * @param fieldType           The field's type.
                 */
                protected WithInstance(String fieldName,
                                       MethodGraph.Compiler methodGraphCompiler,
                                       List<? extends TargetMethodAnnotationDrivenBinder.ParameterBinder<?>> parameterBinders,
                                       ElementMatcher<? super MethodDescription> matcher,
                                       Object target,
                                       TypeDescription.Generic fieldType) {
                    super(fieldName, methodGraphCompiler, parameterBinders, matcher);
                    this.target = target;
                    this.fieldType = fieldType;
                }

                /**
                 * {@inheritDoc}
                 */
                public InstrumentedType prepare(InstrumentedType instrumentedType) {
                    return instrumentedType.withAuxiliaryField(new FieldDescription.Token(fieldName,
                            Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC | Opcodes.ACC_VOLATILE | Opcodes.ACC_SYNTHETIC,
                            fieldType), target);
                }

                @Override
                protected FieldDescription resolve(TypeDescription instrumentedType) {
                    if (!fieldType.asErasure().isVisibleTo(instrumentedType)) {
                        throw new IllegalStateException(fieldType + " is not visible to " + instrumentedType);
                    } else {
                        return instrumentedType.getDeclaredFields()
                                .filter(named(fieldName).and(fieldType(fieldType.asErasure())))
                                .getOnly();
                    }
                }
            }

            /**
             * An implementation target for a field that is declared by the instrumented type or a super type.
             */
            @HashCodeAndEqualsPlugin.Enhance
            protected static class WithLookup extends ForField {

                /**
                 * The field locator factory to use for locating the field to delegate to.
                 */
                private final FieldLocator.Factory fieldLocatorFactory;

                /**
                 * Creates a new implementation delegate for a field that is declared by the instrumented type or any super type.
                 *
                 * @param fieldName           The name of the field that is target of the delegation.
                 * @param methodGraphCompiler The method graph compiler to use.
                 * @param parameterBinders    The parameter binders to use.
                 * @param matcher             The matcher to use for filtering methods.
                 * @param fieldLocatorFactory The field locator factory to use for locating the field to delegate to.
                 */
                protected WithLookup(String fieldName,
                                     MethodGraph.Compiler methodGraphCompiler,
                                     List<? extends TargetMethodAnnotationDrivenBinder.ParameterBinder<?>> parameterBinders,
                                     ElementMatcher<? super MethodDescription> matcher,
                                     FieldLocator.Factory fieldLocatorFactory) {
                    super(fieldName, methodGraphCompiler, parameterBinders, matcher);
                    this.fieldLocatorFactory = fieldLocatorFactory;
                }

                /**
                 * {@inheritDoc}
                 */
                public InstrumentedType prepare(InstrumentedType instrumentedType) {
                    return instrumentedType;
                }

                @Override
                protected FieldDescription resolve(TypeDescription instrumentedType) {
                    FieldLocator.Resolution resolution = fieldLocatorFactory.make(instrumentedType).locate(fieldName);
                    if (!resolution.isResolved()) {
                        throw new IllegalStateException("Could not locate " + fieldName + " on " + instrumentedType);
                    } else {
                        return resolution.getField();
                    }
                }
            }
        }

        /**
         * An implementation delegate for invoking a delegation target on the another methods return value.
         */
        @HashCodeAndEqualsPlugin.Enhance
        class ForMethodReturn implements ImplementationDelegate {

            /**
             * The name of the method to invoke.
             */
            private final String name;

            /**
             * The method graph compiler to use.
             */
            private final MethodGraph.Compiler methodGraphCompiler;

            /**
             * The parameter binders to use.
             */
            private final List<? extends TargetMethodAnnotationDrivenBinder.ParameterBinder<?>> parameterBinders;

            /**
             * The matcher to use for filtering methods.
             */
            private final ElementMatcher<? super MethodDescription> matcher;

            /**
             * Creates a new implementation delegate for a method return value delegation.
             *
             * @param name                The name of the method to invoke.
             * @param methodGraphCompiler The method graph compiler to use.
             * @param parameterBinders    The parameter binders to use.
             * @param matcher             The matcher to use for filtering methods.
             */
            protected ForMethodReturn(String name,
                                      MethodGraph.Compiler methodGraphCompiler,
                                      List<? extends TargetMethodAnnotationDrivenBinder.ParameterBinder<?>> parameterBinders,
                                      ElementMatcher<? super MethodDescription> matcher) {
                this.name = name;
                this.methodGraphCompiler = methodGraphCompiler;
                this.parameterBinders = parameterBinders;
                this.matcher = matcher;
            }

            /**
             * {@inheritDoc}
             */
            public Compiled compile(TypeDescription instrumentedType) {
                MethodList<?> targets = new MethodList.Explicit<MethodDescription>(CompoundList.<MethodDescription>of(
                        instrumentedType.getDeclaredMethods().filter(isStatic().or(isPrivate())),
                        methodGraphCompiler.compile((TypeDefinition) instrumentedType).listNodes().asMethodList())
                ).filter(named(name).and(takesArguments(0)).and(not(returns(isPrimitive().or(isArray())))));
                if (targets.size() != 1) {
                    throw new IllegalStateException(instrumentedType + " does not define method without arguments with name " + name + ": " + targets);
                } else if (!targets.getOnly().getReturnType().asErasure().isVisibleTo(instrumentedType)) {
                    throw new IllegalStateException(targets.getOnly() + " is not visible to " + instrumentedType);
                } else {
                    MethodList<?> candidates = methodGraphCompiler.compile(targets.getOnly().getReturnType(), instrumentedType)
                            .listNodes()
                            .asMethodList()
                            .filter(matcher);
                    List<MethodDelegationBinder.Record> records = new ArrayList<MethodDelegationBinder.Record>(candidates.size());
                    MethodDelegationBinder methodDelegationBinder = TargetMethodAnnotationDrivenBinder.of(parameterBinders);
                    for (MethodDescription candidate : candidates) {
                        records.add(methodDelegationBinder.compile(candidate));
                    }
                    return new Compiled.ForMethodReturn(targets.get(0), records);
                }
            }

            /**
             * {@inheritDoc}
             */
            public InstrumentedType prepare(InstrumentedType instrumentedType) {
                return instrumentedType;
            }
        }

        /**
         * An implementation delegate for constructing an instance.
         */
        @HashCodeAndEqualsPlugin.Enhance
        class ForConstruction implements ImplementationDelegate {

            /**
             * The type being constructed.
             */
            private final TypeDescription typeDescription;

            /**
             * The precompiled delegation records.
             */
            private final List<MethodDelegationBinder.Record> records;

            /**
             * Creates an implementation delegate for constructing a new instance.
             *
             * @param typeDescription The type being constructed.
             * @param records         The precompiled delegation records.
             */
            protected ForConstruction(TypeDescription typeDescription, List<MethodDelegationBinder.Record> records) {
                this.typeDescription = typeDescription;
                this.records = records;
            }

            /**
             * Creates an implementation delegate for constructing a new instance.
             *
             * @param typeDescription        The type being constructed.
             * @param methods                The constructors to consider.
             * @param methodDelegationBinder The method delegation binder to use.
             * @return An appropriate implementation delegate.
             */
            protected static ImplementationDelegate of(TypeDescription typeDescription,
                                                       MethodList<?> methods,
                                                       MethodDelegationBinder methodDelegationBinder) {
                List<MethodDelegationBinder.Record> records = new ArrayList<MethodDelegationBinder.Record>(methods.size());
                for (MethodDescription methodDescription : methods) {
                    records.add(methodDelegationBinder.compile(methodDescription));
                }
                return new ForConstruction(typeDescription, records);
            }

            /**
             * {@inheritDoc}
             */
            public InstrumentedType prepare(InstrumentedType instrumentedType) {
                return instrumentedType;
            }

            /**
             * {@inheritDoc}
             */
            public Compiled compile(TypeDescription instrumentedType) {
                return new Compiled.ForConstruction(typeDescription, records);
            }
        }
    }

    /**
     * The appender for implementing a {@link net.bytebuddy.implementation.MethodDelegation}.
     */
    @HashCodeAndEqualsPlugin.Enhance
    protected static class Appender implements ByteCodeAppender {

        /**
         * The implementation target of this implementation.
         */
        private final Target implementationTarget;

        /**
         * The method delegation binder processor which is responsible for implementing the method delegation.
         */
        private final MethodDelegationBinder.Record processor;

        /**
         * A termination handler for a method delegation binder.
         */
        private final MethodDelegationBinder.TerminationHandler terminationHandler;

        /**
         * The assigner to use.
         */
        private final Assigner assigner;

        /**
         * The compiled implementation delegate.
         */
        private final ImplementationDelegate.Compiled compiled;

        /**
         * Creates a new appender for a method delegation.
         *
         * @param implementationTarget The implementation target of this implementation.
         * @param processor            The method delegation binder processor which is responsible for implementing the method delegation.
         * @param terminationHandler   A termination handler for a method delegation binder.
         * @param assigner             The assigner to use.
         * @param compiled             The compiled implementation delegate.
         */
        protected Appender(Target implementationTarget,
                           MethodDelegationBinder.Record processor,
                           MethodDelegationBinder.TerminationHandler terminationHandler,
                           Assigner assigner,
                           ImplementationDelegate.Compiled compiled) {
            this.implementationTarget = implementationTarget;
            this.processor = processor;
            this.terminationHandler = terminationHandler;
            this.assigner = assigner;
            this.compiled = compiled;
        }

        /**
         * {@inheritDoc}
         */
        public Size apply(MethodVisitor methodVisitor, Context implementationContext, MethodDescription instrumentedMethod) {
            StackManipulation.Size stackSize = new StackManipulation.Compound(
                    compiled.prepare(instrumentedMethod),
                    processor.bind(implementationTarget, instrumentedMethod, terminationHandler, compiled.invoke(), assigner)
            ).apply(methodVisitor, implementationContext);
            return new Size(stackSize.getMaximalSize(), instrumentedMethod.getStackSize());
        }
    }

    /**
     * A {@link MethodDelegation} with custom configuration.
     */
    @HashCodeAndEqualsPlugin.Enhance
    public static class WithCustomProperties {

        /**
         * The ambiguity resolver to use.
         */
        private final MethodDelegationBinder.AmbiguityResolver ambiguityResolver;

        /**
         * The parameter binders to use.
         */
        private final List<TargetMethodAnnotationDrivenBinder.ParameterBinder<?>> parameterBinders;

        /**
         * The binding resolver being used to select the relevant method binding.
         */
        private final MethodDelegationBinder.BindingResolver bindingResolver;

        /**
         * The matcher to use for filtering relevant methods.
         */
        private final ElementMatcher<? super MethodDescription> matcher;

        /**
         * Creates a new method delegation with custom properties that does not filter any methods.
         *
         * @param ambiguityResolver The ambiguity resolver to use.
         * @param parameterBinders  The parameter binders to use.
         */
        protected WithCustomProperties(MethodDelegationBinder.AmbiguityResolver ambiguityResolver,
                                       List<TargetMethodAnnotationDrivenBinder.ParameterBinder<?>> parameterBinders) {
            this(ambiguityResolver, parameterBinders, MethodDelegationBinder.BindingResolver.Default.INSTANCE, any());
        }

        /**
         * Creates a new method delegation with custom properties.
         *
         * @param ambiguityResolver The ambiguity resolver to use.
         * @param parameterBinders  The parameter binders to use.
         * @param bindingResolver   The binding resolver being used to select the relevant method binding.
         * @param matcher           The matcher to use for filtering relevant methods.
         */
        private WithCustomProperties(MethodDelegationBinder.AmbiguityResolver ambiguityResolver,
                                     List<TargetMethodAnnotationDrivenBinder.ParameterBinder<?>> parameterBinders,
                                     MethodDelegationBinder.BindingResolver bindingResolver,
                                     ElementMatcher<? super MethodDescription> matcher) {
            this.ambiguityResolver = ambiguityResolver;
            this.parameterBinders = parameterBinders;
            this.bindingResolver = bindingResolver;
            this.matcher = matcher;
        }

        /**
         * Configures this method delegation to use the supplied ambiguity resolvers when deciding which out of two ore
         * more legal delegation targets should be considered.
         *
         * @param ambiguityResolver The ambiguity resolvers to use in their application order.
         * @return A new delegation configuration which also applies the supplied ambiguity resolvers.
         */
        public WithCustomProperties withResolvers(MethodDelegationBinder.AmbiguityResolver... ambiguityResolver) {
            return withResolvers(Arrays.asList(ambiguityResolver));
        }

        /**
         * Configures this method delegation to use the supplied ambiguity resolvers when deciding which out of two ore
         * more legal delegation targets should be considered.
         *
         * @param ambiguityResolvers The ambiguity resolvers to use in their application order.
         * @return A new delegation configuration which also applies the supplied ambiguity resolvers.
         */
        public WithCustomProperties withResolvers(List<? extends MethodDelegationBinder.AmbiguityResolver> ambiguityResolvers) {
            return new WithCustomProperties(new MethodDelegationBinder.AmbiguityResolver.Compound(CompoundList.of(this.ambiguityResolver,
                    ambiguityResolvers)), parameterBinders, bindingResolver, matcher);
        }

        /**
         * Configures this method delegation to use the supplied parameter binders when deciding what value to assign to
         * a parameter of a delegation target.
         *
         * @param parameterBinder The parameter binders to use.
         * @return A new delegation configuration which also applies the supplied parameter binders.
         */
        public WithCustomProperties withBinders(TargetMethodAnnotationDrivenBinder.ParameterBinder<?>... parameterBinder) {
            return withBinders(Arrays.asList(parameterBinder));
        }

        /**
         * Configures this method delegation to use the supplied parameter binders when deciding what value to assign to
         * a parameter of a delegation target.
         *
         * @param parameterBinders The parameter binders to use.
         * @return A new delegation configuration which also applies the supplied parameter binders.
         */
        public WithCustomProperties withBinders(List<? extends TargetMethodAnnotationDrivenBinder.ParameterBinder<?>> parameterBinders) {
            return new WithCustomProperties(ambiguityResolver, CompoundList.of(this.parameterBinders, parameterBinders), bindingResolver, matcher);
        }

        /**
         * Configures a custom binding resolver which is responsible for choosing a method binding among multiple candidates. Configuring
         * a resolver overrides any previous configuration.
         *
         * @param bindingResolver The binding resolver being used to select the relevant method binding.
         * @return A new delegation configuration which applies the supplied binding resolver.
         */
        public WithCustomProperties withBindingResolver(MethodDelegationBinder.BindingResolver bindingResolver) {
            return new WithCustomProperties(ambiguityResolver, parameterBinders, bindingResolver, matcher);
        }

        /**
         * Configures this method delegation to only consider methods or constructors as a delegation target if they match the supplied matcher.
         *
         * @param matcher The matcher any delegation target needs to match in order to be considered a for delegation.
         * @return A new delegation configuration which only considers methods for delegation if they match the supplied matcher.
         */
        @SuppressWarnings("unchecked")
        public WithCustomProperties filter(ElementMatcher<? super MethodDescription> matcher) {
            return new WithCustomProperties(ambiguityResolver,
                    parameterBinders,
                    bindingResolver,
                    new ElementMatcher.Junction.Conjunction<MethodDescription>(this.matcher, matcher));
        }

        /**
         * Delegates any intercepted method to invoke a {@code static} method that is declared by the supplied type. To be considered
         * a valid delegation target, the target method must be visible and accessible to the instrumented type. This is the case if
         * the target type is either public or in the same package as the instrumented type and if the target method is either public
         * or non-private and in the same package as the instrumented type. Private methods can only be used as a delegation target if
         * the interception is targeting the instrumented type.
         *
         * @param type The target type for the delegation.
         * @return A method delegation that redirects method calls to a static method of the supplied type.
         */
        public MethodDelegation to(Class<?> type) {
            return to(TypeDescription.ForLoadedType.of(type));
        }

        /**
         * Delegates any intercepted method to invoke a {@code static} method that is declared by the supplied type. To be considered
         * a valid delegation target, the target method must be visible and accessible to the instrumented type. This is the case if
         * the target type is either public or in the same package as the instrumented type and if the target method is either public
         * or non-private and in the same package as the instrumented type. Private methods can only be used as a delegation target if
         * the delegation is targeting the instrumented type.
         *
         * @param typeDescription The target type for the delegation.
         * @return A method delegation that redirects method calls to a static method of the supplied type.
         */
        public 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.of(typeDescription.getDeclaredMethods().filter(isStatic().and(matcher)),
                    TargetMethodAnnotationDrivenBinder.of(parameterBinders)), parameterBinders, ambiguityResolver, bindingResolver);
        }

        /**
         * Delegates any intercepted method to invoke a non-{@code static} method that is declared by the supplied type's instance or any
         * of its super types. To be considered a valid delegation target, a method must be visible and accessible to the instrumented type.
         * This is the case if the method's declaring type is either public or in the same package as the instrumented type and if the method
         * is either public or non-private and in the same package as the instrumented type. Private methods can only be used as
         * a delegation target if the delegation is targeting the instrumented type.
         *
         * @param target The target instance for the delegation.
         * @return A method delegation that redirects method calls to a static method of the supplied type.
         */
        public MethodDelegation to(Object target) {
            return to(target, MethodGraph.Compiler.DEFAULT);
        }

        /**
         * Delegates any intercepted method to invoke a non-{@code static} method that is declared by the supplied type's instance or any
         * of its super types. To be considered a valid delegation target, a method must be visible and accessible to the instrumented type.
         * This is the case if the method's declaring type is either public or in the same package as the instrumented type and if the method
         * is either public or non-private and in the same package as the instrumented type. Private methods can only be used as
         * a delegation target if the delegation is targeting the instrumented type.
         *
         * @param target              The target instance for the delegation.
         * @param methodGraphCompiler The method graph compiler to use.
         * @return A method delegation that redirects method calls to a static method of the supplied type.
         */
        public MethodDelegation to(Object target, MethodGraph.Compiler methodGraphCompiler) {
            return to(target, target.getClass(), methodGraphCompiler);
        }

        /**
         * Delegates any intercepted method to invoke a non-{@code static} method that is declared by the supplied type's instance or any
         * of its super types. To be considered a valid delegation target, a method must be visible and accessible to the instrumented type.
         * This is the case if the method's declaring type is either public or in the same package as the instrumented type and if the method
         * is either public or non-private and in the same package as the instrumented type. Private methods can only be used as
         * a delegation target if the delegation is targeting the instrumented type.
         *
         * @param target    The target instance for the delegation.
         * @param fieldName The name of the field that is holding the {@code target} instance.
         * @return A method delegation that redirects method calls to a static method of the supplied type.
         */
        public MethodDelegation to(Object target, String fieldName) {
            return to(target, fieldName, MethodGraph.Compiler.DEFAULT);
        }

        /**
         * Delegates any intercepted method to invoke a non-{@code static} method that is declared by the supplied type's instance or any
         * of its super types. To be considered a valid delegation target, a method must be visible and accessible to the instrumented type.
         * This is the case if the method's declaring type is either public or in the same package as the instrumented type and if the method
         * is either public or non-private and in the same package as the instrumented type. Private methods can only be used as
         * a delegation target if the delegation is targeting the instrumented type.
         *
         * @param target              The target instance for the delegation.
         * @param fieldName           The name of the field that is holding the {@code target} instance.
         * @param methodGraphCompiler The method graph compiler to use.
         * @return A method delegation that redirects method calls to a static method of the supplied type.
         */
        public MethodDelegation to(Object target, String fieldName, MethodGraph.Compiler methodGraphCompiler) {
            return to(target, target.getClass(), fieldName, methodGraphCompiler);
        }

        /**
         * Delegates any intercepted method to invoke a non-{@code static} method that is declared by the supplied type's instance or any
         * of its super types. To be considered a valid delegation target, a method must be visible and accessible to the instrumented type.
         * This is the case if the method's declaring type is either public or in the same package as the instrumented type and if the method
         * is either public or non-private and in the same package as the instrumented type. Private methods can only be used as
         * a delegation target if the delegation is targeting the instrumented type.
         *
         * @param target The target instance for the delegation.
         * @param type   The most specific type of which {@code target} should be considered. Must be a super type of the target's actual type.
         * @return A method delegation that redirects method calls to a static method of the supplied type.
         */
        public MethodDelegation to(Object target, Type type) {
            return to(target, type, MethodGraph.Compiler.DEFAULT);
        }

        /**
         * Delegates any intercepted method to invoke a non-{@code static} method that is declared by the supplied type's instance or any
         * of its super types. To be considered a valid delegation target, a method must be visible and accessible to the instrumented type.
         * This is the case if the method's declaring type is either public or in the same package as the instrumented type and if the method
         * is either public or non-private and in the same package as the instrumented type. Private methods can only be used as
         * a delegation target if the delegation is targeting the instrumented type.
         *
         * @param target              The target instance for the delegation.
         * @param type                The most specific type of which {@code target} should be considered. Must be a super type of the target's actual type.
         * @param methodGraphCompiler The method graph compiler to use.
         * @return A method delegation that redirects method calls to a static method of the supplied type.
         */
        public MethodDelegation to(Object target, Type type, MethodGraph.Compiler methodGraphCompiler) {
            return to(target,
                    type,
                    ImplementationDelegate.FIELD_NAME_PREFIX + "$" + RandomString.hashOf(target),
                    methodGraphCompiler);
        }

        /**
         * Delegates any intercepted method to invoke a non-{@code static} method that is declared by the supplied type's instance or any
         * of its super types. To be considered a valid delegation target, a method must be visible and accessible to the instrumented type.
         * This is the case if the method's declaring type is either public or in the same package as the instrumented type and if the method
         * is either public or non-private and in the same package as the instrumented type. Private methods can only be used as
         * a delegation target if the delegation is targeting the instrumented type.
         *
         * @param target    The target instance for the delegation.
         * @param type      The most specific type of which {@code target} should be considered. Must be a super type of the target's actual type.
         * @param fieldName The name of the field that is holding the {@code target} instance.
         * @return A method delegation that redirects method calls to a static method of the supplied type.
         */
        public MethodDelegation to(Object target, Type type, String fieldName) {
            return to(target, type, fieldName, MethodGraph.Compiler.DEFAULT);
        }

        /**
         * Delegates any intercepted method to invoke a non-{@code static} method that is declared by the supplied type's instance or any
         * of its super types. To be considered a valid delegation target, a method must be visible and accessible to the instrumented type.
         * This is the case if the method's declaring type is either public or in the same package as the instrumented type and if the method
         * is either public or non-private and in the same package as the instrumented type. Private methods can only be used as
         * a delegation target if the delegation is targeting the instrumented type.
         *
         * @param target              The target instance for the delegation.
         * @param type                The most specific type of which {@code target} should be considered. Must be a super type of the target's actual type.
         * @param fieldName           The name of the field that is holding the {@code target} instance.
         * @param methodGraphCompiler The method graph compiler to use.
         * @return A method delegation that redirects method calls to a static method of the supplied type.
         */
        public MethodDelegation to(Object target, Type type, String fieldName, MethodGraph.Compiler methodGraphCompiler) {
            return to(target, TypeDefinition.Sort.describe(type), fieldName, methodGraphCompiler);
        }

        /**
         * Delegates any intercepted method to invoke a non-{@code static} method that is declared by the supplied type's instance or any
         * of its super types. To be considered a valid delegation target, a method must be visible and accessible to the instrumented type.
         * This is the case if the method's declaring type is either public or in the same package as the instrumented type and if the method
         * is either public or non-private and in the same package as the instrumented type. Private methods can only be used as
         * a delegation target if the delegation is targeting the instrumented type.
         *
         * @param target         The target instance for the delegation.
         * @param typeDefinition The most specific type of which {@code target} should be considered. Must be a super type of the target's actual type.
         * @return A method delegation that redirects method calls to a static method of the supplied type.
         */
        public MethodDelegation to(Object target, TypeDefinition typeDefinition) {
            return to(target, typeDefinition, MethodGraph.Compiler.DEFAULT);
        }

        /**
         * Delegates any intercepted method to invoke a non-{@code static} method that is declared by the supplied type's instance or any
         * of its super types. To be considered a valid delegation target, a method must be visible and accessible to the instrumented type.
         * This is the case if the method's declaring type is either public or in the same package as the instrumented type and if the method
         * is either public or non-private and in the same package as the instrumented type. Private methods can only be used as
         * a delegation target if the delegation is targeting the instrumented type.
         *
         * @param target              The target instance for the delegation.
         * @param typeDefinition      The most specific type of which {@code target} should be considered. Must be a super type of the target's actual type.
         * @param methodGraphCompiler The method graph compiler to use.
         * @return A method delegation that redirects method calls to a static method of the supplied type.
         */
        public MethodDelegation to(Object target, TypeDefinition typeDefinition, MethodGraph.Compiler methodGraphCompiler) {
            return to(target,
                    typeDefinition,
                    ImplementationDelegate.FIELD_NAME_PREFIX + "$" + RandomString.hashOf(target),
                    methodGraphCompiler);
        }

        /**
         * Delegates any intercepted method to invoke a non-{@code static} method that is declared by the supplied type's instance or any
         * of its super types. To be considered a valid delegation target, a method must be visible and accessible to the instrumented type.
         * This is the case if the method's declaring type is either public or in the same package as the instrumented type and if the method
         * is either public or non-private and in the same package as the instrumented type. Private methods can only be used as
         * a delegation target if the delegation is targeting the instrumented type.
         *
         * @param target         The target instance for the delegation.
         * @param typeDefinition The most specific type of which {@code target} should be considered. Must be a super type of the target's actual type.
         * @param fieldName      The name of the field that is holding the {@code target} instance.
         * @return A method delegation that redirects method calls to a static method of the supplied type.
         */
        public MethodDelegation to(Object target, TypeDefinition typeDefinition, String fieldName) {
            return to(target, typeDefinition, fieldName, MethodGraph.Compiler.DEFAULT);
        }

        /**
         * Delegates any intercepted method to invoke a non-{@code static} method that is declared by the supplied type's instance or any
         * of its super types. To be considered a valid delegation target, a method must be visible and accessible to the instrumented type.
         * This is the case if the method's declaring type is either public or in the same package as the instrumented type and if the method
         * is either public or non-private and in the same package as the instrumented type. Private methods can only be used as
         * a delegation target if the delegation is targeting the instrumented type.
         *
         * @param target              The target instance for the delegation.
         * @param typeDefinition      The most specific type of which {@code target} should be considered. Must be a super type of the target's actual type.
         * @param fieldName           The name of the field that is holding the {@code target} instance.
         * @param methodGraphCompiler The method graph compiler to use.
         * @return A method delegation that redirects method calls to a static method of the supplied type.
         */
        public MethodDelegation to(Object target, TypeDefinition typeDefinition, String fieldName, MethodGraph.Compiler methodGraphCompiler) {
            if (!typeDefinition.asErasure().isInstance(target)) {
                throw new IllegalArgumentException(target + " is not an instance of " + typeDefinition);
            }
            return new MethodDelegation(new ImplementationDelegate.ForField.WithInstance(fieldName,
                    methodGraphCompiler,
                    parameterBinders,
                    matcher,
                    target,
                    typeDefinition.asGenericType()), parameterBinders, ambiguityResolver, bindingResolver);
        }

        /**
         * Delegates any intercepted method to invoke a constructor of the supplied type. To be considered a valid delegation target,
         * a constructor must be visible and accessible to the instrumented type. This is the case if the constructor's declaring type is
         * either public or in the same package as the instrumented type and if the constructor is either public or non-private and in
         * the same package as the instrumented type. Private constructors can only be used as a delegation target if the delegation is
         * targeting the instrumented type.
         *
         * @param type The type to construct.
         * @return A delegation that redirects method calls to a constructor of the supplied type.
         */
        public MethodDelegation toConstructor(Class<?> type) {
            return toConstructor(TypeDescription.ForLoadedType.of(type));
        }

        /**
         * Delegates any intercepted method to invoke a constructor of the supplied type. To be considered a valid delegation target,
         * a constructor must be visible and accessible to the instrumented type. This is the case if the constructor's declaring type is
         * either public or in the same package as the instrumented type and if the constructor is either public or non-private and in
         * the same package as the instrumented type. Private constructors can only be used as a delegation target if the delegation is
         * targeting the instrumented type.
         *
         * @param typeDescription The type to construct.
         * @return A delegation that redirects method calls to a constructor of the supplied type.
         */
        public MethodDelegation toConstructor(TypeDescription typeDescription) {
            return new MethodDelegation(ImplementationDelegate.ForConstruction.of(typeDescription,
                    typeDescription.getDeclaredMethods().filter(isConstructor().and(matcher)),
                    TargetMethodAnnotationDrivenBinder.of(parameterBinders)), parameterBinders, ambiguityResolver, bindingResolver);
        }

        /**
         * Delegates any intercepted method to invoke a non-{@code static} method on the instance of the supplied field. To be
         * considered a valid delegation target, a method must be visible and accessible to the instrumented type. This is the
         * case if the method's declaring type is either public or in the same package as the instrumented type and if the method
         * is either public or non-private and in the same package as the instrumented type. Private methods can only be used as
         * a delegation target if the delegation is targeting the instrumented type.
         *
         * @param name The field's name.
         * @return A delegation that redirects invocations to a method of the specified field's instance.
         */
        public MethodDelegation toField(String name) {
            return toField(name, FieldLocator.ForClassHierarchy.Factory.INSTANCE);
        }

        /**
         * Delegates any intercepted method to invoke a non-{@code static} method on the instance of the supplied field. To be
         * considered a valid delegation target, a method must be visible and accessible to the instrumented type. This is the
         * case if the method's declaring type is either public or in the same package as the instrumented type and if the method
         * is either public or non-private and in the same package as the instrumented type. Private methods can only be used as
         * a delegation target if the delegation is targeting the instrumented type.
         *
         * @param name                The field's name.
         * @param fieldLocatorFactory The field locator factory to use.
         * @return A delegation that redirects invocations to a method of the specified field's instance.
         */
        public MethodDelegation toField(String name, FieldLocator.Factory fieldLocatorFactory) {
            return toField(name, fieldLocatorFactory, MethodGraph.Compiler.DEFAULT);
        }

        /**
         * Delegates any intercepted method to invoke a non-{@code static} method on the instance of the supplied field. To be
         * considered a valid delegation target, a method must be visible and accessible to the instrumented type. This is the
         * case if the method's declaring type is either public or in the same package as the instrumented type and if the method
         * is either public or non-private and in the same package as the instrumented type. Private methods can only be used as
         * a delegation target if the delegation is targeting the instrumented type.
         *
         * @param name                The field's name.
         * @param methodGraphCompiler The method graph compiler to use.
         * @return A delegation that redirects invocations to a method of the specified field's instance.
         */
        public MethodDelegation toField(String name, MethodGraph.Compiler methodGraphCompiler) {
            return toField(name, FieldLocator.ForClassHierarchy.Factory.INSTANCE, methodGraphCompiler);
        }

        /**
         * Delegates any intercepted method to invoke a non-{@code static} method on the instance of the supplied field. To be
         * considered a valid delegation target, a method must be visible and accessible to the instrumented type. This is the
         * case if the method's declaring type is either public or in the same package as the instrumented type and if the method
         * is either public or non-private and in the same package as the instrumented type. Private methods can only be used as
         * a delegation target if the delegation is targeting the instrumented type.
         *
         * @param name                The field's name.
         * @param fieldLocatorFactory The field locator factory to use.
         * @param methodGraphCompiler The method graph compiler to use.
         * @return A delegation that redirects invocations to a method of the specified field's instance.
         */
        public MethodDelegation toField(String name, FieldLocator.Factory fieldLocatorFactory, MethodGraph.Compiler methodGraphCompiler) {
            return new MethodDelegation(new ImplementationDelegate.ForField.WithLookup(name,
                    methodGraphCompiler,
                    parameterBinders,
                    matcher,
                    fieldLocatorFactory), parameterBinders, ambiguityResolver, bindingResolver);
        }

        /**
         * Delegates any intercepted method to invoke a method on an instance that is returned by a parameterless method of the
         * given name. To be considered a valid delegation target, a method must be visible and accessible to the instrumented type.
         * This is the case if the method's declaring type is either public or in the same package as the instrumented type and if
         * the method is either public or non-private and in the same package as the instrumented type. Private methods can only
         * be used as a delegation target if the delegation is targeting the instrumented type.
         *
         * @param name The name of the method that returns the delegation target.
         * @return A delegation that redirects invocations to the return value of a method that is declared by the instrumented type.
         */
        public MethodDelegation toMethodReturnOf(String name) {
            return toMethodReturnOf(name, MethodGraph.Compiler.DEFAULT);
        }

        /**
         * Delegates any intercepted method to invoke a method on an instance that is returned by a parameterless method of the
         * given name. To be considered a valid delegation target, a method must be visible and accessible to the instrumented type.
         * This is the case if the method's declaring type is either public or in the same package as the instrumented type and if
         * the method is either public or non-private and in the same package as the instrumented type. Private methods can only
         * be used as a delegation target if the delegation is targeting the instrumented type.
         *
         * @param name                The name of the method that returns the delegation target.
         * @param methodGraphCompiler The method graph compiler to use.
         * @return A delegation that redirects invocations to the return value of a method that is declared by the instrumented type.
         */
        public MethodDelegation toMethodReturnOf(String name, MethodGraph.Compiler methodGraphCompiler) {
            return new MethodDelegation(new ImplementationDelegate.ForMethodReturn(name,
                    methodGraphCompiler,
                    parameterBinders,
                    matcher), parameterBinders, ambiguityResolver, bindingResolver);
        }
    }
}
