Skip to content

Commit

Permalink
Add possibility to define custom constructor resolver for super annot…
Browse files Browse the repository at this point in the history
…ation.
  • Loading branch information
raphw committed Oct 22, 2024
1 parent 4a21948 commit 916b41b
Show file tree
Hide file tree
Showing 6 changed files with 109 additions and 27 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import net.bytebuddy.dynamic.scaffold.TypeValidation;
import net.bytebuddy.implementation.Implementation;
import net.bytebuddy.implementation.MethodAccessorFactory;
import net.bytebuddy.implementation.bind.annotation.Super;
import net.bytebuddy.implementation.bytecode.*;
import net.bytebuddy.implementation.bytecode.constant.DefaultValue;
import net.bytebuddy.implementation.bytecode.member.FieldAccess;
Expand Down Expand Up @@ -395,16 +396,13 @@ public static class ForSuperMethodByConstructor extends StackManipulation.Abstra
*/
private final TypeDescription proxiedType;

private final MethodDescription.InDefinedShape constructor;

/**
* The implementation target this type proxy is created for.
*/
private final Implementation.Target implementationTarget;

/**
* The parameter types of the constructor that should be called.
*/
private final List<TypeDescription> constructorParameters;

/**
* {@code true} if any finalizers should be ignored for the delegation.
*/
Expand All @@ -420,18 +418,17 @@ public static class ForSuperMethodByConstructor extends StackManipulation.Abstra
*
* @param proxiedType The type for the type proxy to subclass or implement.
* @param implementationTarget The implementation target this type proxy is created for.
* @param constructorParameters The parameter types of the constructor that should be called.
* @param ignoreFinalizer {@code true} if any finalizers should be ignored for the delegation.
* @param serializableProxy Determines if the proxy should be serializable.
*/
public ForSuperMethodByConstructor(TypeDescription proxiedType,
MethodDescription.InDefinedShape constructor,
Implementation.Target implementationTarget,
List<TypeDescription> constructorParameters,
boolean ignoreFinalizer,
boolean serializableProxy) {
this.proxiedType = proxiedType;
this.constructor = constructor;
this.implementationTarget = implementationTarget;
this.constructorParameters = constructorParameters;
this.ignoreFinalizer = ignoreFinalizer;
this.serializableProxy = serializableProxy;
}
Expand All @@ -440,22 +437,23 @@ public ForSuperMethodByConstructor(TypeDescription proxiedType,
* {@inheritDoc}
*/
public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext) {
TypeDescription proxyType = implementationContext
.register(new TypeProxy(proxiedType,
implementationTarget,
InvocationFactory.Default.SUPER_METHOD,
ignoreFinalizer,
serializableProxy));
StackManipulation[] constructorValue = new StackManipulation[constructorParameters.size()];
TypeDescription proxyType = implementationContext.register(new TypeProxy(proxiedType,
implementationTarget,
InvocationFactory.Default.SUPER_METHOD,
ignoreFinalizer,
serializableProxy));
StackManipulation[] constructorValue = new StackManipulation[constructor.getParameters().size()];
int index = 0;
for (TypeDescription parameterType : constructorParameters) {
for (TypeDescription parameterType : constructor.getParameters().asTypeList().asErasures()) {
constructorValue[index++] = DefaultValue.of(parameterType);
}
return new Compound(
TypeCreation.of(proxyType),
Duplication.SINGLE,
new Compound(constructorValue),
MethodInvocation.invoke(proxyType.getDeclaredMethods().filter(isConstructor().and(takesArguments(constructorParameters))).getOnly()),
MethodInvocation.invoke(proxyType.getDeclaredMethods()
.filter(isConstructor().and(takesArguments(constructor.getParameters().asTypeList().asErasures())))
.getOnly()),
Duplication.SINGLE,
MethodVariableAccess.loadThis(),
FieldAccess.forField(proxyType.getDeclaredFields().filter((named(INSTANCE_FIELD))).getOnly()).write()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,11 @@
import net.bytebuddy.implementation.bytecode.assign.Assigner;

import java.lang.annotation.*;
import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;
import java.util.List;

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

/**
* Parameters that are annotated with this annotation are assigned an instance of an auxiliary proxy type that allows calling
Expand Down Expand Up @@ -102,6 +104,16 @@
*/
Class<?>[] constructorParameters() default {};

/**
* Specifies a class to resolve a constructor of the proxied type to use for instantiation if
* {@link Instantiation#CONSTRUCTOR} is used. Note that the specified class will be loaded and instantiated by
* Byte Buddy in order to resolve the constructor. For this, the specified class requires a public
* default constructor.
*
* @return The type of the {@link ConstructorResolver} to use.
*/
Class<? extends ConstructorResolver> constructorResolver() default ConstructorResolver.Default.class;

/**
* Determines the type that is implemented by the proxy. When this value is set to its default value
* {@code void}, the proxy is created as an instance of the parameter's type. When it is set to
Expand All @@ -112,6 +124,46 @@
*/
Class<?> proxyType() default void.class;

/**
* A constructor resolver is responsible to specify the constructor to be used for creating a proxy.
*/
interface ConstructorResolver {

/**
* Resolves the constructor to be used.
*
* @param proxiedType The type being proxied.
* @param constructorParameters The types being specified on the annotation.
* @return The constructor to invoke with default arguments for instantiation.
*/
MethodDescription.InDefinedShape resolve(TypeDescription proxiedType, List<TypeDescription> constructorParameters);

/**
* A default constructor resolver that attempts to resolve a constructor with the given argument types.
*/
class Default implements ConstructorResolver {

/**
* {@inheritDoc}
*/
public MethodDescription.InDefinedShape resolve(TypeDescription proxiedType, List<TypeDescription> constructorParameters) {
if (proxiedType.isInterface()) {
return TypeDescription.ForLoadedType.of(Object.class).getDeclaredMethods()
.filter(isConstructor())
.getOnly();
}
MethodList<MethodDescription.InDefinedShape> candidates = proxiedType.getDeclaredMethods().filter(isConstructor()
.and(not(isPrivate()))
.and(takesArguments(constructorParameters)));
if (candidates.size() == 1) {
return candidates.getOnly();
} else {
throw new IllegalStateException("Did not discover exactly one constructor on " + proxiedType + " with parameters " + constructorParameters);
}
}
}
}

/**
* Determines the instantiation of the proxy type.
*
Expand All @@ -125,12 +177,32 @@ enum Instantiation {
*/
CONSTRUCTOR {
@Override
protected StackManipulation proxyFor(TypeDescription parameterType,
protected StackManipulation proxyFor(TypeDescription proxyType,
Implementation.Target implementationTarget,
AnnotationDescription.Loadable<Super> annotation) {
return new TypeProxy.ForSuperMethodByConstructor(parameterType,
MethodDescription.InDefinedShape constructor;
try {
@SuppressWarnings("unchecked")
ConstructorResolver constructorResolver = (ConstructorResolver) annotation.getValue(CONSTRUCTOR_RESOLVER)
.load(ConstructorResolver.class.getClassLoader())
.resolve(Class.class)
.getConstructor()
.newInstance();
constructor = constructorResolver.resolve(
proxyType,
Arrays.asList(annotation.getValue(CONSTRUCTOR_PARAMETERS).resolve(TypeDescription[].class)));
} catch (NoSuchMethodException exception) {
throw new IllegalStateException("No default constructor specified by " + annotation.getValue(CONSTRUCTOR_RESOLVER)
.resolve(TypeDescription.class)
.getName(), exception);
} catch (InvocationTargetException exception) {
throw new IllegalStateException("Failed to resolve constructor specified by " + annotation, exception.getTargetException());
} catch (Exception exception) {
throw new IllegalStateException("Failed to resolve constructor specified by " + annotation, exception);
}
return new TypeProxy.ForSuperMethodByConstructor(proxyType,
constructor,
implementationTarget,
Arrays.asList(annotation.getValue(CONSTRUCTOR_PARAMETERS).resolve(TypeDescription[].class)),
annotation.getValue(IGNORE_FINALIZER).resolve(Boolean.class),
annotation.getValue(SERIALIZABLE_PROXY).resolve(Boolean.class));
}
Expand All @@ -142,10 +214,10 @@ protected StackManipulation proxyFor(TypeDescription parameterType,
*/
UNSAFE {
@Override
protected StackManipulation proxyFor(TypeDescription parameterType,
protected StackManipulation proxyFor(TypeDescription proxyType,
Implementation.Target implementationTarget,
AnnotationDescription.Loadable<Super> annotation) {
return new TypeProxy.ForSuperMethodByReflectionFactory(parameterType,
return new TypeProxy.ForSuperMethodByReflectionFactory(proxyType,
implementationTarget,
annotation.getValue(IGNORE_FINALIZER).resolve(Boolean.class),
annotation.getValue(SERIALIZABLE_PROXY).resolve(Boolean.class));
Expand All @@ -167,6 +239,11 @@ protected StackManipulation proxyFor(TypeDescription parameterType,
*/
private static final MethodDescription.InDefinedShape CONSTRUCTOR_PARAMETERS;

/**
* A reference to the constructor parameters resolver method.
*/
private static final MethodDescription.InDefinedShape CONSTRUCTOR_RESOLVER;

/*
* Extracts method references to the annotation methods.
*/
Expand All @@ -175,18 +252,19 @@ protected StackManipulation proxyFor(TypeDescription parameterType,
IGNORE_FINALIZER = annotationProperties.filter(named("ignoreFinalizer")).getOnly();
SERIALIZABLE_PROXY = annotationProperties.filter(named("serializableProxy")).getOnly();
CONSTRUCTOR_PARAMETERS = annotationProperties.filter(named("constructorParameters")).getOnly();
CONSTRUCTOR_RESOLVER = annotationProperties.filter(named("constructorResolver")).getOnly();
}

/**
* Creates a stack manipulation which loads a {@code super}-call proxy onto the stack.
*
* @param parameterType The type of the parameter that was annotated with
* @param proxyType The type of the proxy that is bound to the parameter annotated by
* {@link net.bytebuddy.implementation.bind.annotation.Super}
* @param implementationTarget The implementation target for the currently created type.
* @param annotation The annotation that caused this method call.
* @return A stack manipulation representing this instance's instantiation strategy.
*/
protected abstract StackManipulation proxyFor(TypeDescription parameterType,
protected abstract StackManipulation proxyFor(TypeDescription proxyType,
Implementation.Target implementationTarget,
AnnotationDescription.Loadable<Super> annotation);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -222,8 +222,8 @@ public void testForConstructorConstruction() throws Exception {
.thenReturn(new StackManipulation.Size(0, 0));
when(methodAccessorFactory.registerAccessorFor(specialMethodInvocation, MethodAccessorFactory.AccessType.DEFAULT)).thenReturn(proxyMethod);
StackManipulation stackManipulation = new TypeProxy.ForSuperMethodByConstructor(foo,
new MethodDescription.ForLoadedConstructor(Foo.class.getConstructor(Void.class)),
implementationTarget,
Collections.singletonList((TypeDescription) TypeDescription.ForLoadedType.of(Void.class)),
true,
false);
MethodVisitor methodVisitor = mock(MethodVisitor.class);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package net.bytebuddy.implementation.bind.annotation;

import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.method.MethodList;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.implementation.bind.MethodDelegationBinder;
import net.bytebuddy.implementation.bytecode.assign.Assigner;
Expand Down Expand Up @@ -31,7 +33,10 @@ public void setUp() throws Exception {
when(genericTargetType.asErasure()).thenReturn(targetType);
when(annotation.strategy()).thenReturn(Super.Instantiation.CONSTRUCTOR);
when(annotation.constructorParameters()).thenReturn(new Class<?>[0]);
doReturn(Super.ConstructorResolver.Default.class).when(annotation).constructorResolver();
when(targetType.asErasure()).thenReturn(targetType);
when(targetType.getDeclaredMethods()).thenReturn(new MethodList.Explicit<MethodDescription.InDefinedShape>(
new MethodDescription.ForLoadedConstructor(Object.class.getConstructor())));
}

protected TargetMethodAnnotationDrivenBinder.ParameterBinder<Super> getSimpleBinder() {
Expand Down
2 changes: 1 addition & 1 deletion byte-buddy/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -422,7 +422,7 @@
<plugin>
<groupId>codes.rafael.bytecodeupdate</groupId>
<artifactId>bytecode-update-maven-plugin</artifactId>
<version>1.0-SNAPSHOT</version>
<version>${version.plugin.bytecode-update}</version>
<dependencies>
<dependency>
<groupId>org.ow2.asm</groupId>
Expand Down
1 change: 1 addition & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@
<version.plugin.license>3.0</version.plugin.license>
<version.plugin.japicmp>0.15.7</version.plugin.japicmp>
<version.plugin.antrun>3.1.0</version.plugin.antrun>
<version.plugin.bytecode-update>1.0</version.plugin.bytecode-update>
<version.checkstyle>9.3</version.checkstyle>
<version.android.sdk>4.1.1.4</version.android.sdk>
<version.utility.findbugs>3.0.1</version.utility.findbugs>
Expand Down

0 comments on commit 916b41b

Please sign in to comment.