net.bytebuddy.implementation.bind.annotation.Pipe Maven / Gradle / Ivy
/*
* 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.bind.annotation;
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 net.bytebuddy.utility.RandomString;
import net.bytebuddy.jar.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
* {@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.
*
*
* @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 {@link java.io.Serializable}.
*
* @return {@code true} if the generated proxy should be {@link java.io.Serializable}.
*/
boolean serializableProxy() default false;
/**
* 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 {
/**
* A description of the {@link Pipe#serializableProxy()} method.
*/
private static final MethodDescription.InDefinedShape SERIALIZABLE_PROXY = TypeDescription.ForLoadedType.of(Pipe.class)
.getDeclaredMethods()
.filter(named("serializableProxy"))
.getOnly();
/**
* 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
* {@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 {@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 install(Class> type) {
return install(TypeDescription.ForLoadedType.of(type));
}
/**
* 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 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.
*
* @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}
*/
public Class getHandledType() {
return Pipe.class;
}
/**
* {@inheritDoc}
*/
public MethodDelegationBinder.ParameterBinding> bind(AnnotationDescription.Loadable 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 RedirectionProxy(forwardingMethod.getDeclaringType().asErasure(),
source,
assigner,
annotation.getValue(SERIALIZABLE_PROXY).resolve(Boolean.class)));
}
/**
* 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 RedirectionProxy extends StackManipulation.AbstractBase implements AuxiliaryType {
/**
* 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.
*/
private final TypeDescription forwardingType;
/**
* The method that is to be forwarded.
*/
private final MethodDescription sourceMethod;
/**
* The assigner to use.
*/
private final Assigner assigner;
/**
* Determines if the generated proxy should be {@link java.io.Serializable}.
*/
private final boolean serializableProxy;
/**
* 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 RedirectionProxy(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.
*
* @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 extractFields(MethodDescription methodDescription) {
TypeList parameterTypes = methodDescription.getParameters().asTypeList().asErasures();
LinkedHashMap typeDescriptions = new LinkedHashMap();
int currentIndex = 0;
for (TypeDescription parameterType : parameterTypes) {
typeDescriptions.put(fieldName(currentIndex++), parameterType);
}
return typeDescriptions;
}
/**
* 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}
*/
public String getSuffix() {
return RandomString.hashOf(forwardingType.hashCode())
+ RandomString.hashOf(sourceMethod.hashCode())
+ (serializableProxy ? "S" : "0");
}
/**
* {@inheritDoc}
*/
public DynamicType make(String auxiliaryTypeName,
ClassFileVersion classFileVersion,
MethodAccessorFactory methodAccessorFactory) {
LinkedHashMap 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.isAbstract().and(isDeclaredBy(forwardingType)))
.intercept(new MethodCall(sourceMethod, assigner))
.defineConstructor().withParameters(parameterFields.values())
.intercept(ConstructorCall.INSTANCE);
for (Map.Entry field : parameterFields.entrySet()) {
builder = builder.defineField(field.getKey(), field.getValue(), Visibility.PRIVATE);
}
return builder.make();
}
/**
* {@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 {@link RedirectionProxy}'s constructor.
*/
protected enum ConstructorCall implements Implementation {
/**
* The singleton instance.
*/
INSTANCE;
/**
* A reference of the {@link Object} type default constructor.
*/
private final transient MethodDescription.InDefinedShape objectTypeDefaultConstructor;
/**
* Creates the constructor call singleton.
*/
ConstructorCall() {
objectTypeDefaultConstructor = TypeDescription.ForLoadedType.of(Object.class).getDeclaredMethods().filter(isConstructor()).getOnly();
}
/**
* {@inheritDoc}
*/
public InstrumentedType prepare(InstrumentedType instrumentedType) {
return instrumentedType;
}
/**
* {@inheritDoc}
*/
public ByteCodeAppender appender(Target implementationTarget) {
return new Appender(implementationTarget.getInstrumentedType());
}
/**
* The appender for implementing the {@link RedirectionProxy.ConstructorCall}.
*/
@HashCodeAndEqualsPlugin.Enhance
private static class Appender implements ByteCodeAppender {
/**
* The instrumented type being created.
*/
private final TypeDescription instrumentedType;
/**
* Creates a new appender.
*
* @param instrumentedType The instrumented type that is being created.
*/
private Appender(TypeDescription instrumentedType) {
this.instrumentedType = instrumentedType;
}
/**
* {@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 {@link RedirectionProxy}'s forwarding method.
*/
@HashCodeAndEqualsPlugin.Enhance
protected static class MethodCall implements Implementation {
/**
* The method that is invoked by the implemented method.
*/
private final MethodDescription redirectedMethod;
/**
* The assigner to be used for invoking the forwarded method.
*/
private final Assigner assigner;
/**
* 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}
*/
public InstrumentedType prepare(InstrumentedType instrumentedType) {
return instrumentedType;
}
/**
* {@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 {@link RedirectionProxy.MethodCall}.
*/
@HashCodeAndEqualsPlugin.Enhance(includeSyntheticFields = true)
private class Appender implements ByteCodeAppender {
/**
* The instrumented type that is implemented.
*/
private final TypeDescription instrumentedType;
/**
* Creates a new appender.
*
* @param instrumentedType The instrumented type to be implemented.
*/
private Appender(TypeDescription instrumentedType) {
this.instrumentedType = instrumentedType;
}
/**
* {@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.OfNonGenericType.ForLoadedType.of(Object.class), 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());
}
}
}
}
}
}