/*
* Copyright 2014 - 2019 Rafael Winterhalter
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.bytebuddy.implementation.bind;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import net.bytebuddy.build.HashCodeAndEqualsPlugin;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.implementation.Implementation;
import net.bytebuddy.implementation.bind.annotation.BindingPriority;
import net.bytebuddy.implementation.bytecode.Removal;
import net.bytebuddy.implementation.bytecode.StackManipulation;
import net.bytebuddy.implementation.bytecode.assign.Assigner;
import net.bytebuddy.implementation.bytecode.member.MethodInvocation;
import net.bytebuddy.implementation.bytecode.member.MethodReturn;
import net.bytebuddy.utility.CompoundList;
import org.objectweb.asm.MethodVisitor;
import java.io.PrintStream;
import java.util.*;
A method delegation binder is responsible for creating a method binding for a source method to a
target method. Such a binding allows to implement the source method by calling the target method.
Usually, an implementation will attempt to bind a specific source method to a set of target method candidates where all legal bindings are considered for binding. To chose a specific candidate, an AmbiguityResolver
will be consulted for selecting a best binding.
/**
* A method delegation binder is responsible for creating a method binding for a <i>source method</i> to a
* <i>target method</i>. Such a binding allows to implement the source method by calling the target method.
* <p> </p>
* Usually, an implementation will attempt to bind a specific source method to a set of target method candidates
* where all legal bindings are considered for binding. To chose a specific candidate, an
* {@link net.bytebuddy.implementation.bind.MethodDelegationBinder.AmbiguityResolver}
* will be consulted for selecting a <i>best</i> binding.
*/
public interface MethodDelegationBinder {
Compiles this method delegation binder for a target method.
Params: - candidate – The target method to bind.
Returns: A compiled target for binding.
/**
* Compiles this method delegation binder for a target method.
*
* @param candidate The target method to bind.
* @return A compiled target for binding.
*/
Record compile(MethodDescription candidate);
A method delegation that was compiled to a target method.
/**
* A method delegation that was compiled to a target method.
*/
interface Record {
Attempts a binding of a source method to this compiled target.
Params: - implementationTarget – The target of the current implementation onto which this binding is to be applied.
- source – The method that is to be bound to the
target
method. - terminationHandler – The termination handler to apply.
- methodInvoker – The method invoker to use.
- assigner – The assigner to use.
Returns: A binding representing this attempt to bind the source
method to the target
method.
/**
* Attempts a binding of a source method to this compiled target.
*
* @param implementationTarget The target of the current implementation onto which this binding is to be applied.
* @param source The method that is to be bound to the {@code target} method.
* @param terminationHandler The termination handler to apply.
* @param methodInvoker The method invoker to use.
* @param assigner The assigner to use.
* @return A binding representing this attempt to bind the {@code source} method to the {@code target} method.
*/
MethodBinding bind(Implementation.Target implementationTarget,
MethodDescription source,
TerminationHandler terminationHandler,
MethodInvoker methodInvoker,
Assigner assigner);
A compiled method delegation binder that only yields illegal bindings.
/**
* A compiled method delegation binder that only yields illegal bindings.
*/
enum Illegal implements Record {
The singleton instance.
/**
* The singleton instance.
*/
INSTANCE;
{@inheritDoc}
/**
* {@inheritDoc}
*/
public MethodBinding bind(Implementation.Target implementationTarget,
MethodDescription source,
TerminationHandler terminationHandler,
MethodInvoker methodInvoker,
Assigner assigner) {
return MethodBinding.Illegal.INSTANCE;
}
}
}
Implementations are used as delegates for invoking a method that was bound using a MethodDelegationBinder
. /**
* Implementations are used as delegates for invoking a method that was bound
* using a {@link net.bytebuddy.implementation.bind.MethodDelegationBinder}.
*/
interface MethodInvoker {
Creates a method invocation for a given method.
Params: - methodDescription – The method to be invoked.
Returns: A stack manipulation encapsulating this method invocation.
/**
* Creates a method invocation for a given method.
*
* @param methodDescription The method to be invoked.
* @return A stack manipulation encapsulating this method invocation.
*/
StackManipulation invoke(MethodDescription methodDescription);
A simple method invocation that merely uses the most general form of method invocation as provided by MethodInvocation
. /**
* A simple method invocation that merely uses the most general form of method invocation as provided by
* {@link net.bytebuddy.implementation.bytecode.member.MethodInvocation}.
*/
enum Simple implements MethodInvoker {
The singleton instance.
/**
* The singleton instance.
*/
INSTANCE;
{@inheritDoc}
/**
* {@inheritDoc}
*/
public StackManipulation invoke(MethodDescription methodDescription) {
return MethodInvocation.invoke(methodDescription);
}
}
A method invocation that enforces a virtual invocation that is dispatched on a given type.
/**
* A method invocation that enforces a virtual invocation that is dispatched on a given type.
*/
@HashCodeAndEqualsPlugin.Enhance
class Virtual implements MethodInvoker {
The type on which a method should be invoked virtually.
/**
* The type on which a method should be invoked virtually.
*/
private final TypeDescription typeDescription;
Creates an immutable method invoker that dispatches all methods on a given type.
Params: - typeDescription – The type on which the method is invoked by virtual invocation.
/**
* Creates an immutable method invoker that dispatches all methods on a given type.
*
* @param typeDescription The type on which the method is invoked by virtual invocation.
*/
public Virtual(TypeDescription typeDescription) {
this.typeDescription = typeDescription;
}
{@inheritDoc}
/**
* {@inheritDoc}
*/
public StackManipulation invoke(MethodDescription methodDescription) {
return MethodInvocation.invoke(methodDescription).virtual(typeDescription);
}
}
}
A binding attempt for a single parameter. Implementations of this type are a suggestion of composing a MethodBinding
by using a Builder
. However, method bindings can also be composed without this type which is merely a suggestion. Type parameters: - <T> – The type of the identification token for this parameter binding.
/**
* A binding attempt for a single parameter. Implementations of this type are a suggestion of composing a
* {@link net.bytebuddy.implementation.bind.MethodDelegationBinder.MethodBinding}
* by using a
* {@link net.bytebuddy.implementation.bind.MethodDelegationBinder.MethodBinding.Builder}.
* However, method bindings can also be composed without this type which is merely a suggestion.
*
* @param <T> The type of the identification token for this parameter binding.
*/
interface ParameterBinding<T> extends StackManipulation {
Returns an identification token for this binding.
Returns: An identification token unique to this binding.
/**
* Returns an identification token for this binding.
*
* @return An identification token unique to this binding.
*/
T getIdentificationToken();
A singleton representation of an illegal binding for a method parameter. An illegal binding usually
suggests that a source method cannot be bound to a specific target method.
/**
* A singleton representation of an illegal binding for a method parameter. An illegal binding usually
* suggests that a source method cannot be bound to a specific target method.
*/
enum Illegal implements ParameterBinding<Void> {
The singleton instance.
/**
* The singleton instance.
*/
INSTANCE;
{@inheritDoc}
/**
* {@inheritDoc}
*/
public Void getIdentificationToken() {
throw new IllegalStateException("An illegal binding does not define an identification token");
}
{@inheritDoc}
/**
* {@inheritDoc}
*/
public boolean isValid() {
return false;
}
{@inheritDoc}
/**
* {@inheritDoc}
*/
public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext) {
throw new IllegalStateException("An illegal parameter binding must not be applied");
}
}
An anonymous binding of a target method parameter.
/**
* An anonymous binding of a target method parameter.
*/
@HashCodeAndEqualsPlugin.Enhance
class Anonymous implements ParameterBinding<Object> {
A pseudo-token that is not exposed and therefore anonymous.
/**
* A pseudo-token that is not exposed and therefore anonymous.
*/
@HashCodeAndEqualsPlugin.ValueHandling(HashCodeAndEqualsPlugin.ValueHandling.Sort.IGNORE)
private final Object anonymousToken;
The stack manipulation that represents the loading of the parameter binding onto the stack.
/**
* The stack manipulation that represents the loading of the parameter binding onto the stack.
*/
private final StackManipulation delegate;
Creates a new, anonymous parameter binding.
Params: - delegate – The stack manipulation that is responsible for loading the parameter value for this
target method parameter onto the stack.
/**
* Creates a new, anonymous parameter binding.
*
* @param delegate The stack manipulation that is responsible for loading the parameter value for this
* target method parameter onto the stack.
*/
public Anonymous(StackManipulation delegate) {
this.delegate = delegate;
anonymousToken = new Object();
}
{@inheritDoc}
/**
* {@inheritDoc}
*/
public Object getIdentificationToken() {
return anonymousToken;
}
{@inheritDoc}
/**
* {@inheritDoc}
*/
public boolean isValid() {
return delegate.isValid();
}
{@inheritDoc}
/**
* {@inheritDoc}
*/
public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext) {
return delegate.apply(methodVisitor, implementationContext);
}
}
A uniquely identifiable parameter binding for a target method. Such bindings are usually later processed by a AmbiguityResolver
in order to resolve binding conflicts between several bindable target methods to the same source method. Type parameters: - <T> – The type of the identification token.
See Also:
/**
* A uniquely identifiable parameter binding for a target method. Such bindings are usually later processed by
* a {@link net.bytebuddy.implementation.bind.MethodDelegationBinder.AmbiguityResolver}
* in order to resolve binding conflicts between several bindable target methods to the same source method.
*
* @param <T> The type of the identification token.
* @see net.bytebuddy.implementation.bind.MethodDelegationBinder.AmbiguityResolver
*/
@HashCodeAndEqualsPlugin.Enhance
class Unique<T> implements ParameterBinding<T> {
The token that identifies this parameter binding as unique.
/**
* The token that identifies this parameter binding as unique.
*/
private final T identificationToken;
The stack manipulation that represents the loading of the parameter binding onto the stack.
/**
* The stack manipulation that represents the loading of the parameter binding onto the stack.
*/
private final StackManipulation delegate;
Creates a new unique parameter binding representant.
Params: - delegate – The stack manipulation that loads the argument for this parameter onto the operand stack.
- identificationToken – The token used for identifying this parameter binding.
/**
* Creates a new unique parameter binding representant.
*
* @param delegate The stack manipulation that loads the argument for this parameter onto the operand stack.
* @param identificationToken The token used for identifying this parameter binding.
*/
public Unique(StackManipulation delegate, T identificationToken) {
this.delegate = delegate;
this.identificationToken = identificationToken;
}
A factory method for creating a unique binding that infers the tokens type.
Params: - delegate – The stack manipulation delegate.
- identificationToken – The identification token.
Type parameters: - <S> – The type of the identification token.
Returns: A new instance representing this unique binding.
/**
* A factory method for creating a unique binding that infers the tokens type.
*
* @param delegate The stack manipulation delegate.
* @param identificationToken The identification token.
* @param <S> The type of the identification token.
* @return A new instance representing this unique binding.
*/
public static <S> Unique<S> of(StackManipulation delegate, S identificationToken) {
return new Unique<S>(delegate, identificationToken);
}
{@inheritDoc}
/**
* {@inheritDoc}
*/
public T getIdentificationToken() {
return identificationToken;
}
{@inheritDoc}
/**
* {@inheritDoc}
*/
public boolean isValid() {
return delegate.isValid();
}
{@inheritDoc}
/**
* {@inheritDoc}
*/
public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext) {
return delegate.apply(methodVisitor, implementationContext);
}
}
}
A binding attempt created by a MethodDelegationBinder
. /**
* A binding attempt created by a
* {@link net.bytebuddy.implementation.bind.MethodDelegationBinder}.
*/
interface MethodBinding extends StackManipulation {
Returns the target method's parameter index for a given parameter binding token.
A binding token can be any object that implements valid Object.hashCode()
and Object.equals(Object)
methods in order to look up a given binding. This way, two bindings can be evaluated of having performed a similar type of binding such that these bindings can be compared and a dominant binding can be identified by an AmbiguityResolver
. Furthermore, a binding is implicitly required to insure the uniqueness of such a parameter binding. Params: - parameterBindingToken – A token which is used to identify a specific unique binding for a given parameter
of the target method.
Returns: The target method's parameter index of this binding or null
if no such argument binding was applied for this binding.
/**
* Returns the target method's parameter index for a given parameter binding token.
* <p> </p>
* A binding token can be any object
* that implements valid {@link Object#hashCode()} and {@link Object#equals(Object)} methods in order
* to look up a given binding. This way, two bindings can be evaluated of having performed a similar type of
* binding such that these bindings can be compared and a dominant binding can be identified by an
* {@link net.bytebuddy.implementation.bind.MethodDelegationBinder.AmbiguityResolver}.
* Furthermore, a binding is implicitly required to insure the uniqueness of such a parameter binding.
*
* @param parameterBindingToken A token which is used to identify a specific unique binding for a given parameter
* of the target method.
* @return The target method's parameter index of this binding or {@code null} if no such argument binding
* was applied for this binding.
*/
Integer getTargetParameterIndex(Object parameterBindingToken);
Returns the target method of the method binding attempt.
Returns: The target method to which the
/**
* Returns the target method of the method binding attempt.
*
* @return The target method to which the
*/
MethodDescription getTarget();
Representation of an attempt to bind a source method to a target method that is not applicable.
See Also: - MethodDelegationBinder
/**
* Representation of an attempt to bind a source method to a target method that is not applicable.
*
* @see net.bytebuddy.implementation.bind.MethodDelegationBinder
*/
enum Illegal implements MethodBinding {
The singleton instance.
/**
* The singleton instance.
*/
INSTANCE;
{@inheritDoc}
/**
* {@inheritDoc}
*/
public Integer getTargetParameterIndex(Object parameterBindingToken) {
throw new IllegalStateException("Method is not bound");
}
{@inheritDoc}
/**
* {@inheritDoc}
*/
public MethodDescription getTarget() {
throw new IllegalStateException("Method is not bound");
}
{@inheritDoc}
/**
* {@inheritDoc}
*/
public boolean isValid() {
return false;
}
{@inheritDoc}
/**
* {@inheritDoc}
*/
public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext) {
throw new IllegalStateException("Cannot delegate to an unbound method");
}
}
A mutable builder that allows to compose a MethodBinding
by adding parameter bindings incrementally. /**
* A mutable builder that allows to compose a
* {@link net.bytebuddy.implementation.bind.MethodDelegationBinder.MethodBinding}
* by adding parameter bindings incrementally.
*/
class Builder {
The method invoker for invoking the actual method that is bound.
/**
* The method invoker for invoking the actual method that is bound.
*/
private final MethodInvoker methodInvoker;
The target method that for which a binding is to be constructed by this builder..
/**
* The target method that for which a binding is to be constructed by this builder..
*/
private final MethodDescription candidate;
The current list of stack manipulations for loading values for each parameter onto the operand stack.
/**
* The current list of stack manipulations for loading values for each parameter onto the operand stack.
*/
private final List<StackManipulation> parameterStackManipulations;
A mapping of identification tokens to the parameter index they were bound for.
/**
* A mapping of identification tokens to the parameter index they were bound for.
*/
private final LinkedHashMap<Object, Integer> registeredTargetIndices;
The index of the next parameter that is to be bound.
/**
* The index of the next parameter that is to be bound.
*/
private int nextParameterIndex;
Creates a new builder for the binding of a given method.
Params: - methodInvoker – The method invoker that is used to create the method invocation of the
target
method. - candidate – The target method that is target of the binding.
/**
* Creates a new builder for the binding of a given method.
*
* @param methodInvoker The method invoker that is used to create the method invocation of the {@code target} method.
* @param candidate The target method that is target of the binding.
*/
public Builder(MethodInvoker methodInvoker, MethodDescription candidate) {
this.methodInvoker = methodInvoker;
this.candidate = candidate;
parameterStackManipulations = new ArrayList<StackManipulation>(candidate.getParameters().size());
registeredTargetIndices = new LinkedHashMap<Object, Integer>();
nextParameterIndex = 0;
}
Appends a stack manipulation for the next parameter of the target method.
Params: - parameterBinding – A binding representing the next subsequent parameter of the method.
Returns: false
if the parameterBindingToken
was already bound. A conflicting binding should usually abort the attempt of binding a method and this Builder
should be discarded.
/**
* Appends a stack manipulation for the next parameter of the target method.
*
* @param parameterBinding A binding representing the next subsequent parameter of the method.
* @return {@code false} if the {@code parameterBindingToken} was already bound. A conflicting binding should
* usually abort the attempt of binding a method and this {@code Builder} should be discarded.
*/
public boolean append(ParameterBinding<?> parameterBinding) {
parameterStackManipulations.add(parameterBinding);
return registeredTargetIndices.put(parameterBinding.getIdentificationToken(), nextParameterIndex++) == null;
}
Creates a binding that represents the bindings collected by this Builder
. Params: - terminatingManipulation – A stack manipulation that is applied after the method invocation.
Returns: A binding representing the parameter bindings collected by this builder.
/**
* Creates a binding that represents the bindings collected by this {@code Builder}.
*
* @param terminatingManipulation A stack manipulation that is applied after the method invocation.
* @return A binding representing the parameter bindings collected by this builder.
*/
public MethodBinding build(StackManipulation terminatingManipulation) {
if (candidate.getParameters().size() != nextParameterIndex) {
throw new IllegalStateException("The number of parameters bound does not equal the target's number of parameters");
}
return new Build(candidate,
registeredTargetIndices,
methodInvoker.invoke(candidate),
parameterStackManipulations,
terminatingManipulation);
}
A method binding that was created by a Builder
. /**
* A method binding that was created by a
* {@link net.bytebuddy.implementation.bind.MethodDelegationBinder.MethodBinding.Builder}.
*/
@HashCodeAndEqualsPlugin.Enhance
protected static class Build implements MethodBinding {
The target method this binding represents.
/**
* The target method this binding represents.
*/
private final MethodDescription target;
A map of identification tokens to the indices of their binding parameters.
/**
* A map of identification tokens to the indices of their binding parameters.
*/
private final Map<?, Integer> registeredTargetIndices;
A stack manipulation that represents the actual method invocation.
/**
* A stack manipulation that represents the actual method invocation.
*/
private final StackManipulation methodInvocation;
A list of manipulations that each represent the loading of a parameter value onto the operand stack.
/**
* A list of manipulations that each represent the loading of a parameter value onto the operand stack.
*/
private final List<StackManipulation> parameterStackManipulations;
The stack manipulation that is applied after the method invocation.
/**
* The stack manipulation that is applied after the method invocation.
*/
private final StackManipulation terminatingStackManipulation;
Creates a new method binding.
Params: - target – The target method this binding represents.
- registeredTargetIndices – A map of identification tokens to the indices of their binding
parameters.
- methodInvocation – A stack manipulation that represents the actual method invocation.
- parameterStackManipulations – A list of manipulations that each represent the loading of a
parameter value onto the operand stack.
- terminatingStackManipulation – The stack manipulation that is applied after the method invocation.
/**
* Creates a new method binding.
*
* @param target The target method this binding represents.
* @param registeredTargetIndices A map of identification tokens to the indices of their binding
* parameters.
* @param methodInvocation A stack manipulation that represents the actual method invocation.
* @param parameterStackManipulations A list of manipulations that each represent the loading of a
* parameter value onto the operand stack.
* @param terminatingStackManipulation The stack manipulation that is applied after the method invocation.
*/
protected Build(MethodDescription target,
Map<?, Integer> registeredTargetIndices,
StackManipulation methodInvocation,
List<StackManipulation> parameterStackManipulations,
StackManipulation terminatingStackManipulation) {
this.target = target;
this.registeredTargetIndices = new HashMap<Object, Integer>(registeredTargetIndices);
this.methodInvocation = methodInvocation;
this.parameterStackManipulations = new ArrayList<StackManipulation>(parameterStackManipulations);
this.terminatingStackManipulation = terminatingStackManipulation;
}
{@inheritDoc}
/**
* {@inheritDoc}
*/
public boolean isValid() {
boolean result = methodInvocation.isValid() && terminatingStackManipulation.isValid();
Iterator<StackManipulation> assignment = parameterStackManipulations.iterator();
while (result && assignment.hasNext()) {
result = assignment.next().isValid();
}
return result;
}
{@inheritDoc}
/**
* {@inheritDoc}
*/
public Integer getTargetParameterIndex(Object parameterBindingToken) {
return registeredTargetIndices.get(parameterBindingToken);
}
{@inheritDoc}
/**
* {@inheritDoc}
*/
public MethodDescription getTarget() {
return target;
}
{@inheritDoc}
/**
* {@inheritDoc}
*/
public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext) {
return new Compound(
CompoundList.of(parameterStackManipulations, Arrays.asList(methodInvocation, terminatingStackManipulation))
).apply(methodVisitor, implementationContext);
}
}
}
}
A binding resolver is responsible to choose a method binding between several possible candidates.
/**
* A binding resolver is responsible to choose a method binding between several possible candidates.
*/
interface BindingResolver {
Resolves a method binding for the source
method. Params: - ambiguityResolver – The ambiguity resolver to use.
- source – The source method being bound.
- targets – The possible target candidates. The list contains at least one element.
Returns: The method binding that was chosen.
/**
* Resolves a method binding for the {@code source} method.
*
* @param ambiguityResolver The ambiguity resolver to use.
* @param source The source method being bound.
* @param targets The possible target candidates. The list contains at least one element.
* @return The method binding that was chosen.
*/
MethodBinding resolve(AmbiguityResolver ambiguityResolver, MethodDescription source, List<MethodBinding> targets);
A default implementation of a binding resolver that fully relies on an AmbiguityResolver
. /**
* A default implementation of a binding resolver that fully relies on an {@link AmbiguityResolver}.
*/
enum Default implements BindingResolver {
The singleton instance.
/**
* The singleton instance.
*/
INSTANCE;
Represents the index of the only value of two elements in a list.
/**
* Represents the index of the only value of two elements in a list.
*/
private static final int ONLY = 0;
Represents the index of the left value of two elements in a list.
/**
* Represents the index of the left value of two elements in a list.
*/
private static final int LEFT = 0;
Represents the index of the right value of two elements in a list.
/**
* Represents the index of the right value of two elements in a list.
*/
private static final int RIGHT = 1;
{@inheritDoc}
/**
* {@inheritDoc}
*/
public MethodBinding resolve(AmbiguityResolver ambiguityResolver, MethodDescription source, List<MethodBinding> targets) {
return doResolve(ambiguityResolver, source, new ArrayList<MethodBinding>(targets));
}
Resolves a method binding for the source
method. Params: - ambiguityResolver – The ambiguity resolver to use.
- source – The source method being bound.
- targets – The possible target candidates. The list contains at least one element and is mutable.
Returns: The method binding that was chosen.
/**
* Resolves a method binding for the {@code source} method.
*
* @param ambiguityResolver The ambiguity resolver to use.
* @param source The source method being bound.
* @param targets The possible target candidates. The list contains at least one element and is mutable.
* @return The method binding that was chosen.
*/
private MethodBinding doResolve(AmbiguityResolver ambiguityResolver, MethodDescription source, List<MethodBinding> targets) {
switch (targets.size()) {
case 1:
return targets.get(ONLY);
case 2: {
MethodBinding left = targets.get(LEFT);
MethodBinding right = targets.get(RIGHT);
switch (ambiguityResolver.resolve(source, left, right)) {
case LEFT:
return left;
case RIGHT:
return right;
case AMBIGUOUS:
case UNKNOWN:
throw new IllegalArgumentException("Cannot resolve ambiguous delegation of " + source + " to " + left + " or " + right);
default:
throw new AssertionError();
}
}
default: /* case 3+: */ {
MethodBinding left = targets.get(LEFT);
MethodBinding right = targets.get(RIGHT);
switch (ambiguityResolver.resolve(source, left, right)) {
case LEFT:
targets.remove(RIGHT);
return doResolve(ambiguityResolver, source, targets);
case RIGHT:
targets.remove(LEFT);
return doResolve(ambiguityResolver, source, targets);
case AMBIGUOUS:
case UNKNOWN:
targets.remove(RIGHT); // Remove right element first due to index alteration!
targets.remove(LEFT);
MethodBinding subResult = doResolve(ambiguityResolver, source, targets);
switch (ambiguityResolver.resolve(source, left, subResult).merge(ambiguityResolver.resolve(source, right, subResult))) {
case RIGHT:
return subResult;
case LEFT:
case AMBIGUOUS:
case UNKNOWN:
throw new IllegalArgumentException("Cannot resolve ambiguous delegation of " + source + " to " + left + " or " + right);
default:
throw new AssertionError();
}
default:
throw new IllegalStateException("Unexpected amount of targets: " + targets);
}
}
}
}
}
A binding resolver that only binds a method if it has a unique binding.
/**
* A binding resolver that only binds a method if it has a unique binding.
*/
enum Unique implements BindingResolver {
The singleton instance.
/**
* The singleton instance.
*/
INSTANCE;
Indicates the first index of a list only containing one element.
/**
* Indicates the first index of a list only containing one element.
*/
private static final int ONLY = 0;
{@inheritDoc}
/**
* {@inheritDoc}
*/
public MethodBinding resolve(AmbiguityResolver ambiguityResolver, MethodDescription source, List<MethodBinding> targets) {
if (targets.size() == 1) {
return targets.get(ONLY);
} else {
throw new IllegalStateException(source + " allowed for more than one binding: " + targets);
}
}
}
Binds a method using another resolver and prints the selected binding to a PrintStream
. /**
* Binds a method using another resolver and prints the selected binding to a {@link PrintStream}.
*/
@HashCodeAndEqualsPlugin.Enhance
class StreamWriting implements BindingResolver {
The delegate binding resolver.
/**
* The delegate binding resolver.
*/
private final BindingResolver delegate;
The print stream to bind write the chosen binding to.
/**
* The print stream to bind write the chosen binding to.
*/
private final PrintStream printStream;
Creates a new stream writing binding resolver.
Params: - delegate – The delegate binding resolver.
- printStream – The print stream to bind write the chosen binding to.
/**
* Creates a new stream writing binding resolver.
*
* @param delegate The delegate binding resolver.
* @param printStream The print stream to bind write the chosen binding to.
*/
public StreamWriting(BindingResolver delegate, PrintStream printStream) {
this.delegate = delegate;
this.printStream = printStream;
}
Creates a binding resolver that writes results to System.out
and delegates to the Default
resolver. Returns: An appropriate binding resolver.
/**
* Creates a binding resolver that writes results to {@link System#out} and delegates to the {@link Default} resolver.
*
* @return An appropriate binding resolver.
*/
public static BindingResolver toSystemOut() {
return toSystemOut(Default.INSTANCE);
}
Creates a binding resolver that writes results to System.out
and delegates to the Default
resolver. Params: - bindingResolver – The delegate binding resolver.
Returns: An appropriate binding resolver.
/**
* Creates a binding resolver that writes results to {@link System#out} and delegates to the {@link Default} resolver.
*
* @param bindingResolver The delegate binding resolver.
* @return An appropriate binding resolver.
*/
public static BindingResolver toSystemOut(BindingResolver bindingResolver) {
return new StreamWriting(bindingResolver, System.out);
}
Creates a binding resolver that writes results to System.err
and delegates to the Default
resolver. Returns: An appropriate binding resolver.
/**
* Creates a binding resolver that writes results to {@link System#err} and delegates to the {@link Default} resolver.
*
* @return An appropriate binding resolver.
*/
public static BindingResolver toSystemError() {
return toSystemError(Default.INSTANCE);
}
Creates a binding resolver that writes results to System.err
. Params: - bindingResolver – The delegate binding resolver.
Returns: An appropriate binding resolver.
/**
* Creates a binding resolver that writes results to {@link System#err}.
*
* @param bindingResolver The delegate binding resolver.
* @return An appropriate binding resolver.
*/
public static BindingResolver toSystemError(BindingResolver bindingResolver) {
return new StreamWriting(bindingResolver, System.err);
}
{@inheritDoc}
/**
* {@inheritDoc}
*/
public MethodBinding resolve(AmbiguityResolver ambiguityResolver, MethodDescription source, List<MethodBinding> targets) {
MethodBinding methodBinding = delegate.resolve(ambiguityResolver, source, targets);
printStream.println("Binding " + source + " as delegation to " + methodBinding.getTarget());
return methodBinding;
}
}
}
Implementations of this interface are able to attempt the resolution of two successful bindings of a method
to two different target methods in order to identify a dominating binding.
/**
* Implementations of this interface are able to attempt the resolution of two successful bindings of a method
* to two different target methods in order to identify a dominating binding.
*/
@SuppressFBWarnings(value = "IC_SUPERCLASS_USES_SUBCLASS_DURING_INITIALIZATION", justification = "Safe initialization is implied")
interface AmbiguityResolver {
The default ambiguity resolver to use.
/**
* The default ambiguity resolver to use.
*/
AmbiguityResolver DEFAULT = new MethodDelegationBinder.AmbiguityResolver.Compound(BindingPriority.Resolver.INSTANCE,
DeclaringTypeResolver.INSTANCE,
ArgumentTypeResolver.INSTANCE,
MethodNameEqualityResolver.INSTANCE,
ParameterLengthResolver.INSTANCE);
Attempts to resolve to conflicting bindings.
Params: - source – The source method that was bound to both target methods.
- left – The first successful binding of the
source
method. - right – The second successful binding of the
source
method.
Returns: The resolution state when resolving a conflicting binding where Resolution.LEFT
indicates a successful binding to the left
binding while Resolution.RIGHT
indicates a successful binding to the right
binding.
/**
* Attempts to resolve to conflicting bindings.
*
* @param source The source method that was bound to both target methods.
* @param left The first successful binding of the {@code source} method.
* @param right The second successful binding of the {@code source} method.
* @return The resolution state when resolving a conflicting binding where
* {@link net.bytebuddy.implementation.bind.MethodDelegationBinder.AmbiguityResolver.Resolution#LEFT}
* indicates a successful binding to the {@code left} binding while
* {@link net.bytebuddy.implementation.bind.MethodDelegationBinder.AmbiguityResolver.Resolution#RIGHT}
* indicates a successful binding to the {@code right} binding.
*/
Resolution resolve(MethodDescription source, MethodBinding left, MethodBinding right);
A resolution state of an attempt to resolve two conflicting bindings.
/**
* A resolution state of an attempt to resolve two conflicting bindings.
*/
enum Resolution {
Describes a resolution state where no information about dominance could be gathered.
/**
* Describes a resolution state where no information about dominance could be gathered.
*/
UNKNOWN(true),
Describes a resolution state where the left method dominates the right method.
/**
* Describes a resolution state where the left method dominates the right method.
*/
LEFT(false),
Describes a resolution state where the right method dominates the left method.
/**
* Describes a resolution state where the right method dominates the left method.
*/
RIGHT(false),
Describes a resolution state where both methods have inflicting dominance over each other.
/**
* Describes a resolution state where both methods have inflicting dominance over each other.
*/
AMBIGUOUS(true);
true
if this resolution is unresolved. /**
* {@code true} if this resolution is unresolved.
*/
private final boolean unresolved;
Creates a new resolution.
Params: - unresolved –
true
if this resolution is unresolved.
/**
* Creates a new resolution.
*
* @param unresolved {@code true} if this resolution is unresolved.
*/
Resolution(boolean unresolved) {
this.unresolved = unresolved;
}
Checks if this binding is unresolved.
Returns: true
if this binding is unresolved.
/**
* Checks if this binding is unresolved.
*
* @return {@code true} if this binding is unresolved.
*/
public boolean isUnresolved() {
return unresolved;
}
Merges two resolutions in order to determine their compatibility.
Params: - other – The resolution this resolution is to be checked against.
Returns: The merged resolution.
/**
* Merges two resolutions in order to determine their compatibility.
*
* @param other The resolution this resolution is to be checked against.
* @return The merged resolution.
*/
public Resolution merge(Resolution other) {
switch (this) {
case UNKNOWN:
return other;
case AMBIGUOUS:
return AMBIGUOUS;
case LEFT:
case RIGHT:
return other == UNKNOWN || other == this
? this
: AMBIGUOUS;
default:
throw new AssertionError();
}
}
}
An ambiguity resolver that does not attempt to resolve a conflicting binding.
/**
* An ambiguity resolver that does not attempt to resolve a conflicting binding.
*/
enum NoOp implements AmbiguityResolver {
The singleton instance.
/**
* The singleton instance.
*/
INSTANCE;
{@inheritDoc}
/**
* {@inheritDoc}
*/
public Resolution resolve(MethodDescription source, MethodBinding left, MethodBinding right) {
return Resolution.UNKNOWN;
}
}
An ambiguity resolver that always resolves in the specified direction.
/**
* An ambiguity resolver that always resolves in the specified direction.
*/
enum Directional implements AmbiguityResolver {
A resolver that always resolves to Resolution.LEFT
. /**
* A resolver that always resolves to
* {@link net.bytebuddy.implementation.bind.MethodDelegationBinder.AmbiguityResolver.Resolution#LEFT}.
*/
LEFT(true),
A resolver that always resolves to Resolution.RIGHT
. /**
* A resolver that always resolves to
* {@link net.bytebuddy.implementation.bind.MethodDelegationBinder.AmbiguityResolver.Resolution#RIGHT}.
*/
RIGHT(false);
true
if this instance should resolve to the left side. /**
* {@code true} if this instance should resolve to the left side.
*/
private final boolean left;
Creates a new directional resolver.
Params: - left –
true
if this instance should resolve to the left side.
/**
* Creates a new directional resolver.
*
* @param left {@code true} if this instance should resolve to the left side.
*/
Directional(boolean left) {
this.left = left;
}
{@inheritDoc}
/**
* {@inheritDoc}
*/
public Resolution resolve(MethodDescription source, MethodBinding left, MethodBinding right) {
return this.left
? Resolution.LEFT
: Resolution.RIGHT;
}
}
A chain of AmbiguityResolver
s that are applied in the given order until two bindings can be resolved. /**
* A chain of {@link net.bytebuddy.implementation.bind.MethodDelegationBinder.AmbiguityResolver}s
* that are applied in the given order until two bindings can be resolved.
*/
@HashCodeAndEqualsPlugin.Enhance
class Compound implements AmbiguityResolver {
A list of ambiguity resolvers that are applied by this chain in their order of application.
/**
* A list of ambiguity resolvers that are applied by this chain in their order of application.
*/
private final List<AmbiguityResolver> ambiguityResolvers;
Creates an immutable chain of ambiguity resolvers.
Params: - ambiguityResolver – The ambiguity resolvers to chain in the order of their application.
/**
* Creates an immutable chain of ambiguity resolvers.
*
* @param ambiguityResolver The ambiguity resolvers to chain in the order of their application.
*/
public Compound(AmbiguityResolver... ambiguityResolver) {
this(Arrays.asList(ambiguityResolver));
}
Creates an immutable chain of ambiguity resolvers.
Params: - ambiguityResolvers – The ambiguity resolvers to chain in the order of their application.
/**
* Creates an immutable chain of ambiguity resolvers.
*
* @param ambiguityResolvers The ambiguity resolvers to chain in the order of their application.
*/
public Compound(List<? extends AmbiguityResolver> ambiguityResolvers) {
this.ambiguityResolvers = new ArrayList<AmbiguityResolver>();
for (AmbiguityResolver ambiguityResolver : ambiguityResolvers) {
if (ambiguityResolver instanceof Compound) {
this.ambiguityResolvers.addAll(((Compound) ambiguityResolver).ambiguityResolvers);
} else if (!(ambiguityResolver instanceof NoOp)) {
this.ambiguityResolvers.add(ambiguityResolver);
}
}
}
{@inheritDoc}
/**
* {@inheritDoc}
*/
public Resolution resolve(MethodDescription source, MethodBinding left, MethodBinding right) {
Resolution resolution = Resolution.UNKNOWN;
Iterator<? extends AmbiguityResolver> iterator = ambiguityResolvers.iterator();
while (resolution.isUnresolved() && iterator.hasNext()) {
resolution = iterator.next().resolve(source, left, right);
}
return resolution;
}
}
}
A termination handler is responsible for terminating a method delegation.
/**
* A termination handler is responsible for terminating a method delegation.
*/
interface TerminationHandler {
Creates a stack manipulation that is to be applied after the method return.
Params: - assigner – The supplied assigner.
- typing – The typing to apply.
- source – The source method that is bound to the
target
method. - target – The target method that is subject to be bound by the
source
method.
Returns: A stack manipulation that is applied after the method return.
/**
* Creates a stack manipulation that is to be applied after the method return.
*
* @param assigner The supplied assigner.
* @param typing The typing to apply.
* @param source The source method that is bound to the {@code target} method.
* @param target The target method that is subject to be bound by the {@code source} method.
* @return A stack manipulation that is applied after the method return.
*/
StackManipulation resolve(Assigner assigner, Assigner.Typing typing, MethodDescription source, MethodDescription target);
Responsible for creating a StackManipulation
that is applied after the interception method is applied. /**
* Responsible for creating a {@link StackManipulation}
* that is applied after the interception method is applied.
*/
enum Default implements TerminationHandler {
A termination handler that returns the delegate method's return value.
/**
* A termination handler that returns the delegate method's return value.
*/
RETURNING {
{@inheritDoc} /** {@inheritDoc} */
public StackManipulation resolve(Assigner assigner, Assigner.Typing typing, MethodDescription source, MethodDescription target) {
return new StackManipulation.Compound(assigner.assign(target.isConstructor()
? target.getDeclaringType().asGenericType()
: target.getReturnType(),
source.getReturnType(),
typing), MethodReturn.of(source.getReturnType()));
}
},
A termination handler that drops the delegate method's return value.
/**
* A termination handler that drops the delegate method's return value.
*/
DROPPING {
{@inheritDoc} /** {@inheritDoc} */
public StackManipulation resolve(Assigner assigner, Assigner.Typing typing, MethodDescription source, MethodDescription target) {
return Removal.of(target.isConstructor()
? target.getDeclaringType()
: target.getReturnType());
}
};
}
}
A helper class that allows to identify a best binding for a given type and source method choosing from a list of given target methods by using a given MethodDelegationBinder
and an AmbiguityResolver
.
The Processor
will:
- Try to bind the
source
method using the MethodDelegationBinder
.
- Find a best method among the successful bindings using the
AmbiguityResolver
.
/**
* A helper class that allows to identify a best binding for a given type and source method choosing from a list of given
* target methods by using a given {@link net.bytebuddy.implementation.bind.MethodDelegationBinder}
* and an {@link net.bytebuddy.implementation.bind.MethodDelegationBinder.AmbiguityResolver}.
* <p> </p>
* The {@code Processor} will:
* <ol>
* <li>Try to bind the {@code source} method using the {@code MethodDelegationBinder}.</li>
* <li>Find a best method among the successful bindings using the {@code AmbiguityResolver}.</li>
* </ol>
*/
@HashCodeAndEqualsPlugin.Enhance
class Processor implements MethodDelegationBinder.Record {
The delegation records to consider.
/**
* The delegation records to consider.
*/
private final List<? extends Record> records;
The processor's ambiguity resolver.
/**
* The processor's ambiguity resolver.
*/
private final AmbiguityResolver ambiguityResolver;
The binding resolver being used to select the relevant method binding.
/**
* The binding resolver being used to select the relevant method binding.
*/
private final BindingResolver bindingResolver;
Creates a new processor.
Params: - records – The delegation records to consider.
- ambiguityResolver – The ambiguity resolver to apply.
- bindingResolver – The binding resolver being used to select the relevant method binding.
/**
* Creates a new processor.
*
* @param records The delegation records to consider.
* @param ambiguityResolver The ambiguity resolver to apply.
* @param bindingResolver The binding resolver being used to select the relevant method binding.
*/
public Processor(List<? extends Record> records, AmbiguityResolver ambiguityResolver, BindingResolver bindingResolver) {
this.records = records;
this.ambiguityResolver = ambiguityResolver;
this.bindingResolver = bindingResolver;
}
{@inheritDoc}
/**
* {@inheritDoc}
*/
public MethodBinding bind(Implementation.Target implementationTarget,
MethodDescription source,
TerminationHandler terminationHandler,
MethodInvoker methodInvoker,
Assigner assigner) {
List<MethodBinding> targets = new ArrayList<MethodBinding>();
for (Record record : records) {
MethodBinding methodBinding = record.bind(implementationTarget, source, terminationHandler, methodInvoker, assigner);
if (methodBinding.isValid()) {
targets.add(methodBinding);
}
}
if (targets.isEmpty()) {
throw new IllegalArgumentException("None of " + records + " allows for delegation from " + source);
}
return bindingResolver.resolve(ambiguityResolver, source, targets);
}
}
}