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

import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.ClassFileVersion;
import net.bytebuddy.build.HashCodeAndEqualsPlugin;
import net.bytebuddy.description.annotation.AnnotationDescription;
import net.bytebuddy.description.field.FieldDescription;
import net.bytebuddy.description.field.FieldList;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.method.MethodList;
import net.bytebuddy.description.method.ParameterDescription;
import net.bytebuddy.description.modifier.Visibility;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.description.type.TypeList;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.dynamic.scaffold.InstrumentedType;
import net.bytebuddy.dynamic.scaffold.TypeValidation;
import net.bytebuddy.dynamic.scaffold.subclass.ConstructorStrategy;
import net.bytebuddy.implementation.Implementation;
import net.bytebuddy.implementation.MethodAccessorFactory;
import net.bytebuddy.implementation.auxiliary.AuxiliaryType;
import net.bytebuddy.implementation.bind.MethodDelegationBinder;
import net.bytebuddy.implementation.bytecode.ByteCodeAppender;
import net.bytebuddy.implementation.bytecode.Duplication;
import net.bytebuddy.implementation.bytecode.StackManipulation;
import net.bytebuddy.implementation.bytecode.TypeCreation;
import net.bytebuddy.implementation.bytecode.assign.Assigner;
import net.bytebuddy.implementation.bytecode.member.FieldAccess;
import net.bytebuddy.implementation.bytecode.member.MethodInvocation;
import net.bytebuddy.implementation.bytecode.member.MethodReturn;
import net.bytebuddy.implementation.bytecode.member.MethodVariableAccess;
import net.bytebuddy.matcher.ElementMatchers;
import org.objectweb.asm.MethodVisitor;

import java.io.Serializable;
import java.lang.annotation.*;
import java.util.LinkedHashMap;
import java.util.Map;

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

A target method parameter that is annotated with this annotation allows to forward an intercepted method invocation to another instance. The instance to which a method call is forwarded must be of the most specific type that declares the intercepted method on the intercepted type.

Unfortunately, before Java 8, the Java Class Library does not define any interface type which takes a single Object type and returns another Object type. For this reason, a Binder needs to be installed explicitly and registered on a MethodDelegation. The installed type is allowed to be an interface without any super types that declares a single method which maps an Object type to a another Object type as a result value. It is however not prohibited to use generics in the process.

See Also:
/** * <p> * A target method parameter that is annotated with this annotation allows to forward an intercepted method * invocation to another instance. The instance to which a method call is forwarded must be of the most specific * type that declares the intercepted method on the intercepted type. * </p> * <p> * Unfortunately, before Java 8, the Java Class Library does not define any interface type which takes a single * {@link java.lang.Object} type and returns another {@link java.lang.Object} type. For this reason, a * {@link net.bytebuddy.implementation.bind.annotation.Pipe.Binder} needs to be installed explicitly * and registered on a {@link net.bytebuddy.implementation.MethodDelegation}. The installed type is allowed to be an * interface without any super types that declares a single method which maps an {@link java.lang.Object} type to * a another {@link java.lang.Object} type as a result value. It is however not prohibited to use generics in the * process. * </p> * * @see net.bytebuddy.implementation.MethodDelegation * @see net.bytebuddy.implementation.bind.annotation.TargetMethodAnnotationDrivenBinder */
@Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.PARAMETER) public @interface Pipe {
Determines if the generated proxy should be Serializable.
Returns:true if the generated proxy should be Serializable.
/** * Determines if the generated proxy should be {@link java.io.Serializable}. * * @return {@code true} if the generated proxy should be {@link java.io.Serializable}. */
boolean serializableProxy() default false;
A ParameterBinder for binding the Pipe annotation.
/** * A {@link net.bytebuddy.implementation.bind.annotation.TargetMethodAnnotationDrivenBinder.ParameterBinder} * for binding the {@link net.bytebuddy.implementation.bind.annotation.Pipe} annotation. */
@HashCodeAndEqualsPlugin.Enhance class Binder implements TargetMethodAnnotationDrivenBinder.ParameterBinder<Pipe> {
The method which implements the behavior of forwarding a method invocation. This method needs to define a single non-static method with an Object to Object mapping.
/** * The method which implements the behavior of forwarding a method invocation. This method needs to define * a single non-static method with an {@link java.lang.Object} to {@link java.lang.Object} mapping. */
private final MethodDescription forwardingMethod;
Creates a new binder. This constructor is not doing any validation of the forwarding method and its declaring type. Such validation is normally performed by the install(Class<?>) method.
Params:
  • forwardingMethod – The method which implements the behavior of forwarding a method invocation. This method needs to define a single non-static method with an Object to Object mapping.
/** * Creates a new binder. This constructor is not doing any validation of the forwarding method and its * declaring type. Such validation is normally performed by the * {@link net.bytebuddy.implementation.bind.annotation.Pipe.Binder#install(Class)} * method. * * @param forwardingMethod The method which implements the behavior of forwarding a method invocation. This * method needs to define a single non-static method with an {@link java.lang.Object} * to {@link java.lang.Object} mapping. */
protected Binder(MethodDescription forwardingMethod) { this.forwardingMethod = forwardingMethod; }
Installs a given type for use on a Pipe annotation. The given type must be an interface without any super interfaces and a single method which maps an Object type to another Object type. The use of generics is permitted.
Params:
  • type – The type to install.
Returns:A binder for the Pipe annotation.
/** * Installs a given type for use on a {@link net.bytebuddy.implementation.bind.annotation.Pipe} * annotation. The given type must be an interface without any super interfaces and a single method which * maps an {@link java.lang.Object} type to another {@link java.lang.Object} type. The use of generics is * permitted. * * @param type The type to install. * @return A binder for the {@link net.bytebuddy.implementation.bind.annotation.Pipe} * annotation. */
public static TargetMethodAnnotationDrivenBinder.ParameterBinder<Pipe> install(Class<?> type) { return install(TypeDescription.ForLoadedType.of(type)); }
Installs a given type for use on a Pipe annotation. The given type must be an interface without any super interfaces and a single method which maps an Object type to another Object type. The use of generics is permitted.
Params:
  • typeDescription – The type to install.
Returns:A binder for the Pipe annotation.
/** * Installs a given type for use on a {@link net.bytebuddy.implementation.bind.annotation.Pipe} * annotation. The given type must be an interface without any super interfaces and a single method which * maps an {@link java.lang.Object} type to another {@link java.lang.Object} type. The use of generics is * permitted. * * @param typeDescription The type to install. * @return A binder for the {@link net.bytebuddy.implementation.bind.annotation.Pipe} * annotation. */
public static TargetMethodAnnotationDrivenBinder.ParameterBinder<Pipe> install(TypeDescription typeDescription) { return new Binder(onlyMethod(typeDescription)); }
Locates the only method of a type that is compatible to being overridden for invoking the proxy.
Params:
  • typeDescription – The type that is being installed.
Returns:Its only method after validation.
/** * Locates the only method of a type that is compatible to being overridden for invoking the proxy. * * @param typeDescription The type that is being installed. * @return Its only method after validation. */
private static MethodDescription onlyMethod(TypeDescription typeDescription) { if (!typeDescription.isInterface()) { throw new IllegalArgumentException(typeDescription + " is not an interface"); } else if (!typeDescription.getInterfaces().isEmpty()) { throw new IllegalArgumentException(typeDescription + " must not extend other interfaces"); } else if (!typeDescription.isPublic()) { throw new IllegalArgumentException(typeDescription + " is mot public"); } MethodList<?> methodCandidates = typeDescription.getDeclaredMethods().filter(isAbstract()); if (methodCandidates.size() != 1) { throw new IllegalArgumentException(typeDescription + " must declare exactly one abstract method"); } MethodDescription methodDescription = methodCandidates.getOnly(); if (!methodDescription.getReturnType().asErasure().represents(Object.class)) { throw new IllegalArgumentException(methodDescription + " does not return an Object-type"); } else if (methodDescription.getParameters().size() != 1 || !methodDescription.getParameters().getOnly().getType().asErasure().represents(Object.class)) { throw new IllegalArgumentException(methodDescription + " does not take a single Object-typed argument"); } return methodDescription; }
{@inheritDoc}
/** * {@inheritDoc} */
public Class<Pipe> getHandledType() { return Pipe.class; }
{@inheritDoc}
/** * {@inheritDoc} */
public MethodDelegationBinder.ParameterBinding<?> bind(AnnotationDescription.Loadable<Pipe> annotation, MethodDescription source, ParameterDescription target, Implementation.Target implementationTarget, Assigner assigner, Assigner.Typing typing) { if (!target.getType().asErasure().equals(forwardingMethod.getDeclaringType())) { throw new IllegalStateException("Illegal use of @Pipe for " + target + " which was installed for " + forwardingMethod.getDeclaringType()); } else if (source.isStatic()) { return MethodDelegationBinder.ParameterBinding.Illegal.INSTANCE; } return new MethodDelegationBinder.ParameterBinding.Anonymous(new Redirection(forwardingMethod.getDeclaringType().asErasure(), source, assigner, annotation.load().serializableProxy())); }
An auxiliary type for performing the redirection of a method invocation as requested by the Pipe annotation.
/** * An auxiliary type for performing the redirection of a method invocation as requested by the * {@link net.bytebuddy.implementation.bind.annotation.Pipe} annotation. */
@HashCodeAndEqualsPlugin.Enhance protected static class Redirection implements AuxiliaryType, StackManipulation {
The prefix for naming fields to store method arguments.
/** * The prefix for naming fields to store method arguments. */
private static final String FIELD_NAME_PREFIX = "argument";
The type that declares the method for forwarding a method invocation.
/** * The type that declares the method for forwarding a method invocation. */
private final TypeDescription forwardingType;
The method that is to be forwarded.
/** * The method that is to be forwarded. */
private final MethodDescription sourceMethod;
The assigner to use.
/** * The assigner to use. */
private final Assigner assigner;
Determines if the generated proxy should be Serializable.
/** * Determines if the generated proxy should be {@link java.io.Serializable}. */
private final boolean serializableProxy;
Creates a new redirection.
Params:
  • forwardingType – The type that declares the method for forwarding a method invocation.
  • sourceMethod – The method that is to be forwarded.
  • assigner – The assigner to use.
  • serializableProxy – Determines if the generated proxy should be Serializable.
/** * Creates a new redirection. * * @param forwardingType The type that declares the method for forwarding a method invocation. * @param sourceMethod The method that is to be forwarded. * @param assigner The assigner to use. * @param serializableProxy Determines if the generated proxy should be {@link java.io.Serializable}. */
protected Redirection(TypeDescription forwardingType, MethodDescription sourceMethod, Assigner assigner, boolean serializableProxy) { this.forwardingType = forwardingType; this.sourceMethod = sourceMethod; this.assigner = assigner; this.serializableProxy = serializableProxy; }
Extracts all parameters of a method to fields.
Params:
  • methodDescription – The method to extract the parameters from.
Returns:A linked hash map of field names to the types of these fields representing all parameters of the given method.
/** * Extracts all parameters of a method to fields. * * @param methodDescription The method to extract the parameters from. * @return A linked hash map of field names to the types of these fields representing all parameters of the * given method. */
private static LinkedHashMap<String, TypeDescription> extractFields(MethodDescription methodDescription) { TypeList parameterTypes = methodDescription.getParameters().asTypeList().asErasures(); LinkedHashMap<String, TypeDescription> typeDescriptions = new LinkedHashMap<String, TypeDescription>(); int currentIndex = 0; for (TypeDescription parameterType : parameterTypes) { typeDescriptions.put(fieldName(currentIndex++), parameterType); } return typeDescriptions; }
Creates a new field name.
Params:
  • index – The index of the field.
Returns:The field name that corresponds to the index.
/** * Creates a new field name. * * @param index The index of the field. * @return The field name that corresponds to the index. */
private static String fieldName(int index) { return FIELD_NAME_PREFIX + index; }
{@inheritDoc}
/** * {@inheritDoc} */
public DynamicType make(String auxiliaryTypeName, ClassFileVersion classFileVersion, MethodAccessorFactory methodAccessorFactory) { LinkedHashMap<String, TypeDescription> parameterFields = extractFields(sourceMethod); DynamicType.Builder<?> builder = new ByteBuddy(classFileVersion) .with(TypeValidation.DISABLED) .subclass(forwardingType, ConstructorStrategy.Default.NO_CONSTRUCTORS) .name(auxiliaryTypeName) .modifiers(DEFAULT_TYPE_MODIFIER) .implement(serializableProxy ? new Class<?>[]{Serializable.class} : new Class<?>[0]) .method(ElementMatchers.<MethodDescription>isAbstract().and(isDeclaredBy(forwardingType))) .intercept(new MethodCall(sourceMethod, assigner)) .defineConstructor().withParameters(parameterFields.values()) .intercept(ConstructorCall.INSTANCE); for (Map.Entry<String, TypeDescription> field : parameterFields.entrySet()) { builder = builder.defineField(field.getKey(), field.getValue(), Visibility.PRIVATE); } return builder.make(); }
{@inheritDoc}
/** * {@inheritDoc} */
public boolean isValid() { return true; }
{@inheritDoc}
/** * {@inheritDoc} */
public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext) { TypeDescription forwardingType = implementationContext.register(this); return new Compound( TypeCreation.of(forwardingType), Duplication.SINGLE, MethodVariableAccess.allArgumentsOf(sourceMethod), MethodInvocation.invoke(forwardingType.getDeclaredMethods().filter(isConstructor()).getOnly()) ).apply(methodVisitor, implementationContext); }
The implementation to implement a Redirection's constructor.
/** * The implementation to implement a * {@link net.bytebuddy.implementation.bind.annotation.Pipe.Binder.Redirection}'s * constructor. */
@SuppressFBWarnings(value = "SE_BAD_FIELD", justification = "Fields of enumerations are never serialized") protected enum ConstructorCall implements Implementation {
The singleton instance.
/** * The singleton instance. */
INSTANCE;
A reference of the Object type default constructor.
/** * A reference of the {@link Object} type default constructor. */
private final MethodDescription.InDefinedShape objectTypeDefaultConstructor;
Creates the constructor call singleton.
/** * Creates the constructor call singleton. */
ConstructorCall() { objectTypeDefaultConstructor = TypeDescription.OBJECT.getDeclaredMethods().filter(isConstructor()).getOnly(); }
{@inheritDoc}
/** * {@inheritDoc} */
public InstrumentedType prepare(InstrumentedType instrumentedType) { return instrumentedType; }
{@inheritDoc}
/** * {@inheritDoc} */
public ByteCodeAppender appender(Target implementationTarget) { return new Appender(implementationTarget.getInstrumentedType()); }
The appender for implementing the ConstructorCall.
/** * The appender for implementing the * {@link net.bytebuddy.implementation.bind.annotation.Pipe.Binder.Redirection.ConstructorCall}. */
@HashCodeAndEqualsPlugin.Enhance private static class Appender implements ByteCodeAppender {
The instrumented type being created.
/** * The instrumented type being created. */
private final TypeDescription instrumentedType;
Creates a new appender.
Params:
  • instrumentedType – The instrumented type that is being created.
/** * Creates a new appender. * * @param instrumentedType The instrumented type that is being created. */
private Appender(TypeDescription instrumentedType) { this.instrumentedType = instrumentedType; }
{@inheritDoc}
/** * {@inheritDoc} */
public Size apply(MethodVisitor methodVisitor, Context implementationContext, MethodDescription instrumentedMethod) { FieldList<?> fieldList = instrumentedType.getDeclaredFields(); StackManipulation[] fieldLoading = new StackManipulation[fieldList.size()]; int index = 0; for (FieldDescription fieldDescription : fieldList) { fieldLoading[index] = new StackManipulation.Compound( MethodVariableAccess.loadThis(), MethodVariableAccess.load(instrumentedMethod.getParameters().get(index)), FieldAccess.forField(fieldDescription).write() ); index++; } StackManipulation.Size stackSize = new StackManipulation.Compound( MethodVariableAccess.loadThis(), MethodInvocation.invoke(ConstructorCall.INSTANCE.objectTypeDefaultConstructor), new StackManipulation.Compound(fieldLoading), MethodReturn.VOID ).apply(methodVisitor, implementationContext); return new Size(stackSize.getMaximalSize(), instrumentedMethod.getStackSize()); } } }
The implementation to implement a Redirection's forwarding method.
/** * The implementation to implement a * {@link net.bytebuddy.implementation.bind.annotation.Pipe.Binder.Redirection}'s * forwarding method. */
@HashCodeAndEqualsPlugin.Enhance protected static class MethodCall implements Implementation {
The method that is invoked by the implemented method.
/** * The method that is invoked by the implemented method. */
private final MethodDescription redirectedMethod;
The assigner to be used for invoking the forwarded method.
/** * The assigner to be used for invoking the forwarded method. */
private final Assigner assigner;
Creates a new method call implementation.
Params:
  • redirectedMethod – The method that is invoked by the implemented method.
  • assigner – The assigner to be used for invoking the forwarded method.
/** * Creates a new method call implementation. * * @param redirectedMethod The method that is invoked by the implemented method. * @param assigner The assigner to be used for invoking the forwarded method. */
private MethodCall(MethodDescription redirectedMethod, Assigner assigner) { this.redirectedMethod = redirectedMethod; this.assigner = assigner; }
{@inheritDoc}
/** * {@inheritDoc} */
public InstrumentedType prepare(InstrumentedType instrumentedType) { return instrumentedType; }
{@inheritDoc}
/** * {@inheritDoc} */
public ByteCodeAppender appender(Target implementationTarget) { if (!redirectedMethod.isAccessibleTo(implementationTarget.getInstrumentedType())) { throw new IllegalStateException("Cannot invoke " + redirectedMethod + " from outside of class via @Pipe proxy"); } return new Appender(implementationTarget.getInstrumentedType()); }
The appender for implementing the MethodCall.
/** * The appender for implementing the * {@link net.bytebuddy.implementation.bind.annotation.Pipe.Binder.Redirection.MethodCall}. */
@HashCodeAndEqualsPlugin.Enhance(includeSyntheticFields = true) private class Appender implements ByteCodeAppender {
The instrumented type that is implemented.
/** * The instrumented type that is implemented. */
private final TypeDescription instrumentedType;
Creates a new appender.
Params:
  • instrumentedType – The instrumented type to be implemented.
/** * Creates a new appender. * * @param instrumentedType The instrumented type to be implemented. */
private Appender(TypeDescription instrumentedType) { this.instrumentedType = instrumentedType; }
{@inheritDoc}
/** * {@inheritDoc} */
public Size apply(MethodVisitor methodVisitor, Context implementationContext, MethodDescription instrumentedMethod) { FieldList<?> fieldList = instrumentedType.getDeclaredFields(); StackManipulation[] fieldLoading = new StackManipulation[fieldList.size()]; int index = 0; for (FieldDescription fieldDescription : fieldList) { fieldLoading[index++] = new StackManipulation.Compound(MethodVariableAccess.loadThis(), FieldAccess.forField(fieldDescription).read()); } StackManipulation.Size stackSize = new StackManipulation.Compound( MethodVariableAccess.REFERENCE.loadFrom(1), assigner.assign(TypeDescription.Generic.OBJECT, redirectedMethod.getDeclaringType().asGenericType(), Assigner.Typing.DYNAMIC), new StackManipulation.Compound(fieldLoading), MethodInvocation.invoke(redirectedMethod), assigner.assign(redirectedMethod.getReturnType(), instrumentedMethod.getReturnType(), Assigner.Typing.DYNAMIC), MethodReturn.REFERENCE ).apply(methodVisitor, implementationContext); return new Size(stackSize.getMaximalSize(), instrumentedMethod.getStackSize()); } } } } } }