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

com.fitbur.mockito.bytebuddy.implementation.bind.annotation.Pipe Maven / Gradle / Ivy

The newest version!
package com.fitbur.mockito.bytebuddy.implementation.bind.annotation;

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

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

import static com.fitbur.mockito.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 com.fitbur.mockito.bytebuddy.implementation.bind.annotation.Pipe.Binder} needs to be installed explicitly * and registered on a {@link com.fitbur.mockito.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. The following example demonstrates how the {@code @Pipe} annotation can be installed on a user type. * As a preparation, one needs to define a type for which the {@code @Pipe} implements the forwarding behavior: *
 * interface Forwarder<T, S> {
 *   T forwardTo(S s);
 * }
 * 
* Based on this type, one can now implement an interceptor: *
 * class Interceptor {
 *   private final Foo foo;
 *
 *   public Interceptor(Foo foo) {
 *     this.foo = foo;
 *   }
 *
 *   public String intercept(@Pipe Forwarder<String, Foo> forwarder) {
 *     return forwarder.forwardTo(foo);
 *   }
 * }
 * 
* Using both of these types, one can now install the * {@link com.fitbur.mockito.bytebuddy.implementation.bind.annotation.Pipe.Binder} and register it on a * {@link com.fitbur.mockito.bytebuddy.implementation.MethodDelegation}: *
 * MethodDelegation
 *   .to(new Interceptor(new Foo()))
 *   .appendParameterBinder(Pipe.Binder.install(ForwardingType.class))
 * 
* * @see com.fitbur.mockito.bytebuddy.implementation.MethodDelegation * @see com.fitbur.mockito.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 com.fitbur.mockito.bytebuddy.implementation.bind.annotation.TargetMethodAnnotationDrivenBinder.ParameterBinder} * for binding the {@link com.fitbur.mockito.bytebuddy.implementation.bind.annotation.Pipe} annotation. */ class Binder implements TargetMethodAnnotationDrivenBinder.ParameterBinder { /** * 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 com.fitbur.mockito.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 com.fitbur.mockito.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 com.fitbur.mockito.bytebuddy.implementation.bind.annotation.Pipe} * annotation. */ public static TargetMethodAnnotationDrivenBinder.ParameterBinder install(Class type) { return install(new TypeDescription.ForLoadedType(type)); } /** * Installs a given type for use on a {@link com.fitbur.mockito.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 com.fitbur.mockito.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; } @Override public Class getHandledType() { return Pipe.class; } @Override public MethodDelegationBinder.ParameterBinding bind(AnnotationDescription.Loadable annotation, MethodDescription source, ParameterDescription target, Implementation.Target implementationTarget, Assigner assigner) { 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.loadSilent().serializableProxy())); } @Override public boolean equals(Object other) { return this == other || !(other == null || getClass() != other.getClass()) && forwardingMethod.equals(((Binder) other).forwardingMethod); } @Override public int hashCode() { return forwardingMethod.hashCode(); } @Override public String toString() { return "Pipe.Binder{forwardingMethod=" + forwardingMethod + '}'; } /** * An auxiliary type for performing the redirection of a method invocation as requested by the * {@link com.fitbur.mockito.bytebuddy.implementation.bind.annotation.Pipe} annotation. */ protected static class Redirection implements AuxiliaryType, StackManipulation { /** * 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 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. * * @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 String.format("%s%d", FIELD_NAME_PREFIX, index); } @Override public DynamicType make(String auxiliaryTypeName, ClassFileVersion classFileVersion, MethodAccessorFactory methodAccessorFactory) { LinkedHashMap parameterFields = extractFields(sourceMethod); DynamicType.Builder builder = new ByteBuddy(classFileVersion) .subclass(forwardingType, ConstructorStrategy.Default.NO_CONSTRUCTORS) .name(auxiliaryTypeName) .modifiers(DEFAULT_TYPE_MODIFIER) .implement(serializableProxy ? new Class[]{Serializable.class} : new Class[0]) .method(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(); } @Override public boolean isValid() { return true; } @Override 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); } @Override public boolean equals(Object other) { if (this == other) return true; if (other == null || getClass() != other.getClass()) return false; Redirection that = (Redirection) other; return serializableProxy == that.serializableProxy && assigner.equals(that.assigner) && forwardingType.equals(that.forwardingType) && sourceMethod.equals(that.sourceMethod); } @Override public int hashCode() { int result = forwardingType.hashCode(); result = 31 * result + sourceMethod.hashCode(); result = 31 * result + assigner.hashCode(); result = 31 * result + (serializableProxy ? 1 : 0); return result; } @Override public String toString() { return "Pipe.Binder.Redirection{" + "forwardingType=" + forwardingType + ", sourceMethod=" + sourceMethod + ", assigner=" + assigner + ", serializableProxy=" + serializableProxy + '}'; } /** * The implementation to implement a * {@link com.fitbur.mockito.bytebuddy.implementation.bind.annotation.Pipe.Binder.Redirection}'s * constructor. */ @SuppressFBWarnings(value = "SE_BAD_FIELD", justification = "Enumerations are not serialized by field") protected enum ConstructorCall implements Implementation { /** * The singleton instance. */ INSTANCE; /** * A reference of the {@link Object} type default constructor. */ private final MethodDescription.InDefinedShape objectTypeDefaultConstructor; /** * Creates the constructor call singleton. */ ConstructorCall() { objectTypeDefaultConstructor = TypeDescription.OBJECT.getDeclaredMethods().filter(isConstructor()).getOnly(); } @Override public InstrumentedType prepare(InstrumentedType instrumentedType) { return instrumentedType; } @Override public ByteCodeAppender appender(Target implementationTarget) { return new Appender(implementationTarget.getInstrumentedType()); } @Override public String toString() { return "Pipe.Binder.Redirection.ConstructorCall." + name(); } /** * The appender for implementing the * {@link com.fitbur.mockito.bytebuddy.implementation.bind.annotation.Pipe.Binder.Redirection.ConstructorCall}. */ 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; } @Override public Size apply(MethodVisitor methodVisitor, Context implementationContext, MethodDescription instrumentedMethod) { StackManipulation thisReference = MethodVariableAccess.REFERENCE.loadOffset(0); FieldList fieldList = instrumentedType.getDeclaredFields(); StackManipulation[] fieldLoading = new StackManipulation[fieldList.size()]; int index = 0; for (FieldDescription fieldDescription : fieldList) { fieldLoading[index] = new StackManipulation.Compound( thisReference, MethodVariableAccess.of(fieldDescription.getType().asErasure()) .loadOffset(instrumentedMethod.getParameters().get(index).getOffset()), FieldAccess.forField(fieldDescription).putter() ); index++; } StackManipulation.Size stackSize = new StackManipulation.Compound( thisReference, MethodInvocation.invoke(ConstructorCall.INSTANCE.objectTypeDefaultConstructor), new StackManipulation.Compound(fieldLoading), MethodReturn.VOID ).apply(methodVisitor, implementationContext); return new Size(stackSize.getMaximalSize(), instrumentedMethod.getStackSize()); } @Override public boolean equals(Object other) { return this == other || !(other == null || getClass() != other.getClass()) && instrumentedType.equals(((Appender) other).instrumentedType); } @Override public int hashCode() { return instrumentedType.hashCode(); } @Override public String toString() { return "Pipe.Binder.Redirection.ConstructorCall.Appender{instrumentedType=" + instrumentedType + '}'; } } } /** * The implementation to implement a * {@link com.fitbur.mockito.bytebuddy.implementation.bind.annotation.Pipe.Binder.Redirection}'s * forwarding method. */ 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; } @Override public InstrumentedType prepare(InstrumentedType instrumentedType) { return instrumentedType; } @Override public ByteCodeAppender appender(Target implementationTarget) { return new Appender(implementationTarget.getInstrumentedType()); } @Override public boolean equals(Object other) { return this == other || !(other == null || getClass() != other.getClass()) && redirectedMethod.equals(((MethodCall) other).redirectedMethod) && assigner.equals(((MethodCall) other).assigner); } @Override public int hashCode() { return redirectedMethod.hashCode() + 31 * assigner.hashCode(); } @Override public String toString() { return "Pipe.Binder.Redirection.MethodCall{" + "redirectedMethod=" + redirectedMethod + ", assigner=" + assigner + '}'; } /** * The appender for implementing the * {@link com.fitbur.mockito.bytebuddy.implementation.bind.annotation.Pipe.Binder.Redirection.MethodCall}. */ 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; } @Override public Size apply(MethodVisitor methodVisitor, Context implementationContext, MethodDescription instrumentedMethod) { StackManipulation thisReference = MethodVariableAccess.of(instrumentedType).loadOffset(0); FieldList fieldList = instrumentedType.getDeclaredFields(); StackManipulation[] fieldLoading = new StackManipulation[fieldList.size()]; int index = 0; for (FieldDescription fieldDescription : fieldList) { fieldLoading[index++] = new StackManipulation.Compound(thisReference, FieldAccess.forField(fieldDescription).getter()); } StackManipulation.Size stackSize = new StackManipulation.Compound( MethodVariableAccess.REFERENCE.loadOffset(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()); } /** * Returns the outer instance. * * @return The outer instance. */ private MethodCall getMethodCall() { return MethodCall.this; } @Override public boolean equals(Object other) { return this == other || !(other == null || getClass() != other.getClass()) && instrumentedType.equals(((Appender) other).instrumentedType) && MethodCall.this.equals(((Appender) other).getMethodCall()); } @Override public int hashCode() { return 31 * MethodCall.this.hashCode() + instrumentedType.hashCode(); } @Override public String toString() { return "Pipe.Binder.Redirection.MethodCall.Appender{" + "methodCall=" + MethodCall.this + ", instrumentedType=" + instrumentedType + '}'; } } } } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy