From 916b41b93e4f4b6e93010144c5b8dd3d7354708e Mon Sep 17 00:00:00 2001 From: Rafael Winterhalter Date: Tue, 22 Oct 2024 20:26:42 +0200 Subject: [PATCH] Add possibility to define custom constructor resolver for super annotation. --- .../implementation/auxiliary/TypeProxy.java | 32 +++---- .../implementation/bind/annotation/Super.java | 94 +++++++++++++++++-- .../auxiliary/TypeProxyCreationTest.java | 2 +- .../bind/annotation/SuperBinderTest.java | 5 + byte-buddy/pom.xml | 2 +- pom.xml | 1 + 6 files changed, 109 insertions(+), 27 deletions(-) diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/auxiliary/TypeProxy.java b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/auxiliary/TypeProxy.java index d2650fcfe1..edf244bbd9 100644 --- a/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/auxiliary/TypeProxy.java +++ b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/auxiliary/TypeProxy.java @@ -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; @@ -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 constructorParameters; - /** * {@code true} if any finalizers should be ignored for the delegation. */ @@ -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 constructorParameters, boolean ignoreFinalizer, boolean serializableProxy) { this.proxiedType = proxiedType; + this.constructor = constructor; this.implementationTarget = implementationTarget; - this.constructorParameters = constructorParameters; this.ignoreFinalizer = ignoreFinalizer; this.serializableProxy = serializableProxy; } @@ -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() diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bind/annotation/Super.java b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bind/annotation/Super.java index 828dfc0e2a..bf6358a25c 100644 --- a/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bind/annotation/Super.java +++ b/byte-buddy-dep/src/main/java/net/bytebuddy/implementation/bind/annotation/Super.java @@ -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 @@ -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 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 @@ -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 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 constructorParameters) { + if (proxiedType.isInterface()) { + return TypeDescription.ForLoadedType.of(Object.class).getDeclaredMethods() + .filter(isConstructor()) + .getOnly(); + } + MethodList 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. * @@ -125,12 +177,32 @@ enum Instantiation { */ CONSTRUCTOR { @Override - protected StackManipulation proxyFor(TypeDescription parameterType, + protected StackManipulation proxyFor(TypeDescription proxyType, Implementation.Target implementationTarget, AnnotationDescription.Loadable 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)); } @@ -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 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)); @@ -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. */ @@ -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 annotation); } diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/auxiliary/TypeProxyCreationTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/auxiliary/TypeProxyCreationTest.java index 23dcb72be3..344c3d0a1a 100644 --- a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/auxiliary/TypeProxyCreationTest.java +++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/auxiliary/TypeProxyCreationTest.java @@ -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); diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/annotation/SuperBinderTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/annotation/SuperBinderTest.java index 71e01b12e3..bf24f42a22 100644 --- a/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/annotation/SuperBinderTest.java +++ b/byte-buddy-dep/src/test/java/net/bytebuddy/implementation/bind/annotation/SuperBinderTest.java @@ -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; @@ -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( + new MethodDescription.ForLoadedConstructor(Object.class.getConstructor()))); } protected TargetMethodAnnotationDrivenBinder.ParameterBinder getSimpleBinder() { diff --git a/byte-buddy/pom.xml b/byte-buddy/pom.xml index 4054f404b2..df540d59cd 100644 --- a/byte-buddy/pom.xml +++ b/byte-buddy/pom.xml @@ -422,7 +422,7 @@ codes.rafael.bytecodeupdate bytecode-update-maven-plugin - 1.0-SNAPSHOT + ${version.plugin.bytecode-update} org.ow2.asm diff --git a/pom.xml b/pom.xml index 4a239d81f0..7632dcd998 100644 --- a/pom.xml +++ b/pom.xml @@ -107,6 +107,7 @@ 3.0 0.15.7 3.1.0 + 1.0 9.3 4.1.1.4 3.0.1