From c164b918c0f2900e3bb3e9662432235a45f5a68b Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Wed, 24 Aug 2022 15:12:46 +0200 Subject: [PATCH 1/3] Apply consistent RuntimeHints defaults This commit harmonizes the registration of an executable so that the default method and the method that takes an empty customizer produces the same hint. The same applies to the readable flag of a field hint. Rather than returning a list of executable modes, the "highest" mode is retained. See gh-29011 --- .../InstanceSupplierCodeGeneratorTests.java | 2 +- .../BindingReflectionHintsRegistrarTests.java | 12 +- ...linBindingReflectionHintsRegistrarTests.kt | 5 +- .../aot/agent/InstrumentedMethodTests.java | 12 +- .../aot/hint/ExecutableHint.java | 36 +++--- .../aot/hint/ExecutableMode.java | 11 ++ .../springframework/aot/hint/FieldHint.java | 7 +- .../aot/hint/ReflectionHints.java | 3 +- .../annotation/SimpleReflectiveProcessor.java | 9 +- .../predicate/ReflectionHintsPredicates.java | 10 +- .../aot/nativex/ReflectionHintsWriter.java | 4 +- .../aot/hint/ExecutableModeTests.java | 60 ++++++++++ .../aot/hint/ReflectionHintsTests.java | 109 ++++++++++++++++-- .../aot/hint/TypeHintTests.java | 39 ++++++- .../SimpleReflectiveProcessorTests.java | 4 +- .../ReflectionHintsPredicatesTests.java | 21 ++-- .../FileNativeConfigurationWriterTests.java | 2 +- .../nativex/ReflectionHintsWriterTests.java | 6 +- .../MessageMappingReflectiveProcessor.java | 4 +- .../RequestMappingReflectiveProcessor.java | 4 +- 20 files changed, 273 insertions(+), 87 deletions(-) create mode 100644 spring-core/src/test/java/org/springframework/aot/hint/ExecutableModeTests.java diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/aot/InstanceSupplierCodeGeneratorTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/aot/InstanceSupplierCodeGeneratorTests.java index e00d3a7792c..78378f6b0a1 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/aot/InstanceSupplierCodeGeneratorTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/aot/InstanceSupplierCodeGeneratorTests.java @@ -283,7 +283,7 @@ class InstanceSupplierCodeGeneratorTests { } private ThrowingConsumer hasMode(ExecutableMode mode) { - return hint -> assertThat(hint.getModes()).containsExactly(mode); + return hint -> assertThat(hint.getMode()).isEqualTo(mode); } @SuppressWarnings("unchecked") diff --git a/spring-context/src/test/java/org/springframework/context/aot/BindingReflectionHintsRegistrarTests.java b/spring-context/src/test/java/org/springframework/context/aot/BindingReflectionHintsRegistrarTests.java index 76330a04f58..d3d4b4de389 100644 --- a/spring-context/src/test/java/org/springframework/context/aot/BindingReflectionHintsRegistrarTests.java +++ b/spring-context/src/test/java/org/springframework/context/aot/BindingReflectionHintsRegistrarTests.java @@ -77,7 +77,7 @@ public class BindingReflectionHintsRegistrarTests { assertThat(typeHint.getType()).isEqualTo(TypeReference.of(SampleClassWithGetter.class)); assertThat(typeHint.methods()).singleElement().satisfies(methodHint -> { assertThat(methodHint.getName()).isEqualTo("getName"); - assertThat(methodHint.getModes()).containsOnly(ExecutableMode.INVOKE); + assertThat(methodHint.getMode()).isEqualTo(ExecutableMode.INVOKE); }); }); } @@ -97,7 +97,7 @@ public class BindingReflectionHintsRegistrarTests { assertThat(typeHint.getType()).isEqualTo(TypeReference.of(SampleClassWithSetter.class)); assertThat(typeHint.methods()).singleElement().satisfies(methodHint -> { assertThat(methodHint.getName()).isEqualTo("setName"); - assertThat(methodHint.getModes()).containsOnly(ExecutableMode.INVOKE); + assertThat(methodHint.getMode()).isEqualTo(ExecutableMode.INVOKE); }); }); } @@ -125,11 +125,11 @@ public class BindingReflectionHintsRegistrarTests { assertThat(typeHint.methods()).satisfiesExactlyInAnyOrder( methodHint -> { assertThat(methodHint.getName()).isEqualTo("setNames"); - assertThat(methodHint.getModes()).containsOnly(ExecutableMode.INVOKE); + assertThat(methodHint.getMode()).isEqualTo(ExecutableMode.INVOKE); }, methodHint -> { assertThat(methodHint.getName()).isEqualTo("getNames"); - assertThat(methodHint.getModes()).containsOnly(ExecutableMode.INVOKE); + assertThat(methodHint.getMode()).isEqualTo(ExecutableMode.INVOKE); }); }); } @@ -180,7 +180,7 @@ public class BindingReflectionHintsRegistrarTests { assertThat(typeHint.methods()).singleElement().satisfies( methodHint -> { assertThat(methodHint.getName()).isEqualTo("getResolvableType"); - assertThat(methodHint.getModes()).containsOnly(ExecutableMode.INVOKE); + assertThat(methodHint.getMode()).isEqualTo(ExecutableMode.INVOKE); }); }); } @@ -218,7 +218,7 @@ public class BindingReflectionHintsRegistrarTests { assertThat(typeHint.getType()).isEqualTo(TypeReference.of(SampleRecord.class)); assertThat(typeHint.methods()).singleElement().satisfies(methodHint -> { assertThat(methodHint.getName()).isEqualTo("name"); - assertThat(methodHint.getModes()).containsOnly(ExecutableMode.INVOKE); + assertThat(methodHint.getMode()).isEqualTo(ExecutableMode.INVOKE); }); }); } diff --git a/spring-context/src/test/kotlin/org/springframework/context/aot/KotlinBindingReflectionHintsRegistrarTests.kt b/spring-context/src/test/kotlin/org/springframework/context/aot/KotlinBindingReflectionHintsRegistrarTests.kt index be7bc11d58c..c59c8fc01da 100644 --- a/spring-context/src/test/kotlin/org/springframework/context/aot/KotlinBindingReflectionHintsRegistrarTests.kt +++ b/spring-context/src/test/kotlin/org/springframework/context/aot/KotlinBindingReflectionHintsRegistrarTests.kt @@ -48,8 +48,7 @@ class KotlinBindingReflectionHintsRegistrarTests { assertThat(typeHint.methods()).singleElement() .satisfies(ThrowingConsumer { methodHint: ExecutableHint -> assertThat(methodHint.name).isEqualTo("getName") - assertThat(methodHint.modes) - .containsOnly(ExecutableMode.INVOKE) + assertThat(methodHint.mode).isEqualTo(ExecutableMode.INVOKE) }) }, ThrowingConsumer { typeHint: TypeHint -> @@ -57,7 +56,7 @@ class KotlinBindingReflectionHintsRegistrarTests { assertThat(typeHint.methods()).singleElement() .satisfies(ThrowingConsumer { methodHint: ExecutableHint -> assertThat(methodHint.name).isEqualTo("serializer") - assertThat(methodHint.modes).containsOnly(ExecutableMode.INVOKE) + assertThat(methodHint.mode).isEqualTo(ExecutableMode.INVOKE) }) }) } diff --git a/spring-core-test/src/test/java/org/springframework/aot/agent/InstrumentedMethodTests.java b/spring-core-test/src/test/java/org/springframework/aot/agent/InstrumentedMethodTests.java index 483638bf675..b6f09e3d118 100644 --- a/spring-core-test/src/test/java/org/springframework/aot/agent/InstrumentedMethodTests.java +++ b/spring-core-test/src/test/java/org/springframework/aot/agent/InstrumentedMethodTests.java @@ -152,14 +152,14 @@ class InstrumentedMethodTests { @Test void classGetConstructorShouldMatchInstrospectConstructorHint() { hints.reflection().registerType(String.class, typeHint -> typeHint.withConstructor(Collections.emptyList(), - constructorHint -> constructorHint.setModes(ExecutableMode.INTROSPECT))); + constructorHint -> constructorHint.withMode(ExecutableMode.INTROSPECT))); assertThatInvocationMatches(InstrumentedMethod.CLASS_GETCONSTRUCTOR, this.stringGetConstructor); } @Test void classGetConstructorShouldMatchInvokeConstructorHint() { hints.reflection().registerType(String.class, typeHint -> typeHint.withConstructor(Collections.emptyList(), - constructorHint -> constructorHint.setModes(ExecutableMode.INVOKE))); + constructorHint -> constructorHint.withMode(ExecutableMode.INVOKE))); assertThatInvocationMatches(InstrumentedMethod.CLASS_GETCONSTRUCTOR, this.stringGetConstructor); } @@ -202,14 +202,14 @@ class InstrumentedMethodTests { @Test void classGetDeclaredConstructorShouldMatchInstrospectConstructorHint() { hints.reflection().registerType(String.class, typeHint -> typeHint.withConstructor(TypeReference.listOf(byte[].class, byte.class), - constructorHint -> constructorHint.setModes(ExecutableMode.INTROSPECT))); + constructorHint -> constructorHint.withMode(ExecutableMode.INTROSPECT))); assertThatInvocationMatches(InstrumentedMethod.CLASS_GETDECLAREDCONSTRUCTOR, this.stringGetDeclaredConstructor); } @Test void classGetDeclaredConstructorShouldMatchInvokeConstructorHint() { hints.reflection().registerType(String.class, typeHint -> typeHint.withConstructor(TypeReference.listOf(byte[].class, byte.class), - constructorHint -> constructorHint.setModes(ExecutableMode.INVOKE))); + constructorHint -> constructorHint.withMode(ExecutableMode.INVOKE))); assertThatInvocationMatches(InstrumentedMethod.CLASS_GETDECLAREDCONSTRUCTOR, this.stringGetDeclaredConstructor); } @@ -378,14 +378,14 @@ class InstrumentedMethodTests { @Test void classGetMethodShouldMatchIntrospectMethodHint() { hints.reflection().registerType(String.class, typeHint -> - typeHint.withMethod("toString", Collections.emptyList(), methodHint -> methodHint.setModes(ExecutableMode.INTROSPECT))); + typeHint.withMethod("toString", Collections.emptyList(), methodHint -> methodHint.withMode(ExecutableMode.INTROSPECT))); assertThatInvocationMatches(InstrumentedMethod.CLASS_GETMETHOD, this.stringGetToStringMethod); } @Test void classGetMethodShouldMatchInvokeMethodHint() { hints.reflection().registerType(String.class, typeHint -> - typeHint.withMethod("toString", Collections.emptyList(), methodHint -> methodHint.setModes(ExecutableMode.INVOKE))); + typeHint.withMethod("toString", Collections.emptyList(), methodHint -> methodHint.withMode(ExecutableMode.INVOKE))); assertThatInvocationMatches(InstrumentedMethod.CLASS_GETMETHOD, this.stringGetToStringMethod); } diff --git a/spring-core/src/main/java/org/springframework/aot/hint/ExecutableHint.java b/spring-core/src/main/java/org/springframework/aot/hint/ExecutableHint.java index db06fafd890..cd11fdc1b01 100644 --- a/spring-core/src/main/java/org/springframework/aot/hint/ExecutableHint.java +++ b/spring-core/src/main/java/org/springframework/aot/hint/ExecutableHint.java @@ -19,12 +19,9 @@ package org.springframework.aot.hint; import java.lang.reflect.Constructor; import java.lang.reflect.Executable; import java.lang.reflect.Method; -import java.util.Arrays; -import java.util.LinkedHashSet; import java.util.List; -import java.util.Set; -import org.springframework.util.ObjectUtils; +import org.springframework.lang.Nullable; /** * A hint that describes the need for reflection on a {@link Method} or @@ -37,13 +34,13 @@ public final class ExecutableHint extends MemberHint { private final List parameterTypes; - private final List modes; + private final ExecutableMode mode; private ExecutableHint(Builder builder) { super(builder.name); this.parameterTypes = List.copyOf(builder.parameterTypes); - this.modes = List.copyOf(builder.modes); + this.mode = (builder.mode != null ? builder.mode : ExecutableMode.INVOKE); } /** @@ -75,11 +72,11 @@ public final class ExecutableHint extends MemberHint { } /** - * Return the {@linkplain ExecutableMode modes} that apply to this hint. + * Return the {@linkplain ExecutableMode mode} that apply to this hint. * @return the modes */ - public List getModes() { - return this.modes; + public ExecutableMode getMode() { + return this.mode; } @@ -92,7 +89,8 @@ public final class ExecutableHint extends MemberHint { private final List parameterTypes; - private final Set modes = new LinkedHashSet<>(); + @Nullable + private ExecutableMode mode; Builder(String name, List parameterTypes) { @@ -101,12 +99,14 @@ public final class ExecutableHint extends MemberHint { } /** - * Add the specified {@linkplain ExecutableMode mode} if necessary. - * @param mode the mode to add + * Specify that the {@linkplain ExecutableMode mode} is required. + * @param mode the required mode * @return {@code this}, to facilitate method chaining */ public Builder withMode(ExecutableMode mode) { - this.modes.add(mode); + if (this.mode == null || !this.mode.includes(mode)) { + this.mode = mode; + } return this; } @@ -114,11 +114,15 @@ public final class ExecutableHint extends MemberHint { * Set the {@linkplain ExecutableMode modes} to use. * @param modes the mode to use * @return {@code this}, to facilitate method chaining + * @deprecated only a single mode can be set, use {@link #withMode(ExecutableMode)} instead */ + @Deprecated public Builder setModes(ExecutableMode... modes) { - this.modes.clear(); - if (!ObjectUtils.isEmpty(modes)) { - this.modes.addAll(Arrays.asList(modes)); + if (modes.length > 1) { + throw new UnsupportedOperationException(); + } + if (modes.length == 1) { + withMode(modes[0]); } return this; } diff --git a/spring-core/src/main/java/org/springframework/aot/hint/ExecutableMode.java b/spring-core/src/main/java/org/springframework/aot/hint/ExecutableMode.java index 9d5599ad83b..7082859e750 100644 --- a/spring-core/src/main/java/org/springframework/aot/hint/ExecutableMode.java +++ b/spring-core/src/main/java/org/springframework/aot/hint/ExecutableMode.java @@ -18,6 +18,8 @@ package org.springframework.aot.hint; import java.lang.reflect.Executable; +import org.springframework.lang.Nullable; + /** * Represent the need of reflection for a given {@link Executable}. * @@ -37,4 +39,13 @@ public enum ExecutableMode { */ INVOKE; + /** + * Specify if this mode already includes the specified {@code other} mode. + * @param other the other mode to check + * @return {@code true} if this mode includes the other mode + */ + boolean includes(@Nullable ExecutableMode other) { + return (other == null || this.ordinal() >= other.ordinal()); + } + } diff --git a/spring-core/src/main/java/org/springframework/aot/hint/FieldHint.java b/spring-core/src/main/java/org/springframework/aot/hint/FieldHint.java index d6976f20349..593d5b5588c 100644 --- a/spring-core/src/main/java/org/springframework/aot/hint/FieldHint.java +++ b/spring-core/src/main/java/org/springframework/aot/hint/FieldHint.java @@ -18,6 +18,8 @@ package org.springframework.aot.hint; import java.lang.reflect.Field; +import org.springframework.lang.Nullable; + /** * A hint that describes the need of reflection on a {@link Field}. * @@ -33,7 +35,7 @@ public final class FieldHint extends MemberHint { private FieldHint(Builder builder) { super(builder.name); - this.allowWrite = builder.allowWrite; + this.allowWrite = (builder.allowWrite != null) ? builder.allowWrite : true; this.allowUnsafeAccess = builder.allowUnsafeAccess; } @@ -61,7 +63,8 @@ public final class FieldHint extends MemberHint { private final String name; - private boolean allowWrite; + @Nullable + private Boolean allowWrite; private boolean allowUnsafeAccess; diff --git a/spring-core/src/main/java/org/springframework/aot/hint/ReflectionHints.java b/spring-core/src/main/java/org/springframework/aot/hint/ReflectionHints.java index c5386cfdbb7..b9460ed01bf 100644 --- a/spring-core/src/main/java/org/springframework/aot/hint/ReflectionHints.java +++ b/spring-core/src/main/java/org/springframework/aot/hint/ReflectionHints.java @@ -165,8 +165,7 @@ public class ReflectionHints { * @return {@code this}, to facilitate method chaining */ public ReflectionHints registerConstructor(Constructor constructor) { - return registerConstructor(constructor, constructorHint -> - constructorHint.withMode(ExecutableMode.INVOKE)); + return registerConstructor(constructor, constructorHint -> constructorHint.withMode(ExecutableMode.INVOKE)); } /** diff --git a/spring-core/src/main/java/org/springframework/aot/hint/annotation/SimpleReflectiveProcessor.java b/spring-core/src/main/java/org/springframework/aot/hint/annotation/SimpleReflectiveProcessor.java index 4ad5e9363dd..6d4d87277a5 100644 --- a/spring-core/src/main/java/org/springframework/aot/hint/annotation/SimpleReflectiveProcessor.java +++ b/spring-core/src/main/java/org/springframework/aot/hint/annotation/SimpleReflectiveProcessor.java @@ -20,10 +20,7 @@ import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; -import java.util.function.Consumer; -import org.springframework.aot.hint.ExecutableHint.Builder; -import org.springframework.aot.hint.ExecutableMode; import org.springframework.aot.hint.ReflectionHints; /** @@ -36,8 +33,6 @@ import org.springframework.aot.hint.ReflectionHints; */ public class SimpleReflectiveProcessor implements ReflectiveProcessor { - private static final Consumer INVOKE_EXECUTABLE = hint -> hint.setModes(ExecutableMode.INVOKE); - @Override public void registerReflectionHints(ReflectionHints hints, AnnotatedElement element) { if (element instanceof Class type) { @@ -69,7 +64,7 @@ public class SimpleReflectiveProcessor implements ReflectiveProcessor { * @param constructor the constructor to process */ protected void registerConstructorHint(ReflectionHints hints, Constructor constructor) { - hints.registerConstructor(constructor, INVOKE_EXECUTABLE); + hints.registerConstructor(constructor); } /** @@ -87,7 +82,7 @@ public class SimpleReflectiveProcessor implements ReflectiveProcessor { * @param method the method to process */ protected void registerMethodHint(ReflectionHints hints, Method method) { - hints.registerMethod(method, INVOKE_EXECUTABLE); + hints.registerMethod(method); } } diff --git a/spring-core/src/main/java/org/springframework/aot/hint/predicate/ReflectionHintsPredicates.java b/spring-core/src/main/java/org/springframework/aot/hint/predicate/ReflectionHintsPredicates.java index cd4c902a848..61b7dbd8456 100644 --- a/spring-core/src/main/java/org/springframework/aot/hint/predicate/ReflectionHintsPredicates.java +++ b/spring-core/src/main/java/org/springframework/aot/hint/predicate/ReflectionHintsPredicates.java @@ -263,11 +263,11 @@ public class ReflectionHintsPredicates { * and the configured {@code ExecutableMode} is compatibe */ static boolean includes(ExecutableHint hint, String name, - List parameterTypes, List executableModes) { + List parameterTypes, ExecutableMode executableModes) { return hint.getName().equals(name) && hint.getParameterTypes().equals(parameterTypes) - && (hint.getModes().contains(ExecutableMode.INVOKE) - || !executableModes.contains(ExecutableMode.INVOKE)); + && (hint.getMode().equals(ExecutableMode.INVOKE) + || !executableModes.equals(ExecutableMode.INVOKE)); } } @@ -302,7 +302,7 @@ public class ReflectionHintsPredicates { List parameters = Arrays.stream(this.executable.getParameterTypes()) .map(TypeReference::of).toList(); return includes(executableHint, "", - parameters, List.of(this.executableMode)); + parameters, this.executableMode); }); } @@ -341,7 +341,7 @@ public class ReflectionHintsPredicates { List parameters = Arrays.stream(this.executable.getParameterTypes()) .map(TypeReference::of).toList(); return includes(executableHint, this.executable.getName(), - parameters, List.of(this.executableMode)); + parameters, this.executableMode); }); } diff --git a/spring-core/src/main/java/org/springframework/aot/nativex/ReflectionHintsWriter.java b/spring-core/src/main/java/org/springframework/aot/nativex/ReflectionHintsWriter.java index bd222342108..d370570c9a8 100644 --- a/spring-core/src/main/java/org/springframework/aot/nativex/ReflectionHintsWriter.java +++ b/spring-core/src/main/java/org/springframework/aot/nativex/ReflectionHintsWriter.java @@ -85,10 +85,10 @@ class ReflectionHintsWriter { private void handleExecutables(Map attributes, List hints) { addIfNotEmpty(attributes, "methods", hints.stream() - .filter(h -> h.getModes().contains(ExecutableMode.INVOKE) || h.getModes().isEmpty()) + .filter(h -> h.getMode().equals(ExecutableMode.INVOKE)) .map(this::toAttributes).toList()); addIfNotEmpty(attributes, "queriedMethods", hints.stream() - .filter(h -> h.getModes().contains(ExecutableMode.INTROSPECT)) + .filter(h -> h.getMode().equals(ExecutableMode.INTROSPECT)) .map(this::toAttributes).toList()); } diff --git a/spring-core/src/test/java/org/springframework/aot/hint/ExecutableModeTests.java b/spring-core/src/test/java/org/springframework/aot/hint/ExecutableModeTests.java new file mode 100644 index 00000000000..d19df791de2 --- /dev/null +++ b/spring-core/src/test/java/org/springframework/aot/hint/ExecutableModeTests.java @@ -0,0 +1,60 @@ +/* + * Copyright 2002-2022 the original author or authors. + * + * 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 + * + * https://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 org.springframework.aot.hint; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link ExecutableMode}. + * + * @author Stephane Nicoll + */ +class ExecutableModeTests { + + @Test + void invokeIncludesNullMode() { + assertThat(ExecutableMode.INVOKE.includes(null)).isTrue(); + } + + @Test + void invokeIncludesIntrospect() { + assertThat(ExecutableMode.INVOKE.includes(ExecutableMode.INTROSPECT)).isTrue(); + } + + @Test + void invokeIncludesIncludes() { + assertThat(ExecutableMode.INVOKE.includes(ExecutableMode.INVOKE)).isTrue(); + } + + @Test + void introspectIncludesNullMode() { + assertThat(ExecutableMode.INTROSPECT.includes(null)).isTrue(); + } + + @Test + void introspectIncludesIntrospect() { + assertThat(ExecutableMode.INTROSPECT.includes(ExecutableMode.INTROSPECT)).isTrue(); + } + + @Test + void introspectDoesNotIncludeInvoke() { + assertThat(ExecutableMode.INTROSPECT.includes(ExecutableMode.INVOKE)).isFalse(); + } + +} diff --git a/spring-core/src/test/java/org/springframework/aot/hint/ReflectionHintsTests.java b/spring-core/src/test/java/org/springframework/aot/hint/ReflectionHintsTests.java index 68458881dde..24799c99da8 100644 --- a/spring-core/src/test/java/org/springframework/aot/hint/ReflectionHintsTests.java +++ b/spring-core/src/test/java/org/springframework/aot/hint/ReflectionHintsTests.java @@ -121,14 +121,46 @@ class ReflectionHintsTests { } @Test - void registerField() { + void registerFieldAllowsWriteByDefault() { Field field = ReflectionUtils.findField(TestType.class, "field"); assertThat(field).isNotNull(); this.reflectionHints.registerField(field); + assertTestTypeFieldHint(fieldHint -> { + assertThat(fieldHint.getName()).isEqualTo("field"); + assertThat(fieldHint.isAllowWrite()).isTrue(); + assertThat(fieldHint.isAllowUnsafeAccess()).isFalse(); + }); + } + + @Test + void registerFieldWithEmptyCustomizerAppliesConsistentDefault() { + Field field = ReflectionUtils.findField(TestType.class, "field"); + assertThat(field).isNotNull(); + this.reflectionHints.registerField(field, fieldHint -> {}); + assertTestTypeFieldHint(fieldHint -> { + assertThat(fieldHint.getName()).isEqualTo("field"); + assertThat(fieldHint.isAllowWrite()).isTrue(); + assertThat(fieldHint.isAllowUnsafeAccess()).isFalse(); + }); + } + + @Test + void registerFieldWithCustomizerAppliesCustomization() { + Field field = ReflectionUtils.findField(TestType.class, "field"); + assertThat(field).isNotNull(); + this.reflectionHints.registerField(field, fieldHint -> + fieldHint.allowWrite(false).allowUnsafeAccess(true)); + assertTestTypeFieldHint(fieldHint -> { + assertThat(fieldHint.getName()).isEqualTo("field"); + assertThat(fieldHint.isAllowWrite()).isFalse(); + assertThat(fieldHint.isAllowUnsafeAccess()).isTrue(); + }); + } + + private void assertTestTypeFieldHint(Consumer fieldHint) { assertThat(this.reflectionHints.typeHints()).singleElement().satisfies(typeHint -> { assertThat(typeHint.getType().getCanonicalName()).isEqualTo(TestType.class.getCanonicalName()); - assertThat(typeHint.fields()).singleElement().satisfies(fieldHint -> - assertThat(fieldHint.getName()).isEqualTo("field")); + assertThat(typeHint.fields()).singleElement().satisfies(fieldHint); assertThat(typeHint.constructors()).isEmpty(); assertThat(typeHint.methods()).isEmpty(); assertThat(typeHint.getMemberCategories()).isEmpty(); @@ -138,14 +170,38 @@ class ReflectionHintsTests { @Test void registerConstructor() { this.reflectionHints.registerConstructor(TestType.class.getDeclaredConstructors()[0]); + assertTestTypeConstructorHint(constructorHint -> { + assertThat(constructorHint.getParameterTypes()).isEmpty(); + assertThat(constructorHint.getMode()).isEqualTo(ExecutableMode.INVOKE); + }); + } + + @Test + void registerConstructorWithEmptyCustomizerAppliesConsistentDefault() { + this.reflectionHints.registerConstructor(TestType.class.getDeclaredConstructors()[0], + constructorHint -> {}); + assertTestTypeConstructorHint(constructorHint -> { + assertThat(constructorHint.getParameterTypes()).isEmpty(); + assertThat(constructorHint.getMode()).isEqualTo(ExecutableMode.INVOKE); + }); + } + + @Test + void registerConstructorWithCustomizerAppliesCustomization() { + this.reflectionHints.registerConstructor(TestType.class.getDeclaredConstructors()[0], + constructorHint -> constructorHint.withMode(ExecutableMode.INTROSPECT)); + assertTestTypeConstructorHint(constructorHint -> { + assertThat(constructorHint.getParameterTypes()).isEmpty(); + assertThat(constructorHint.getMode()).isEqualTo(ExecutableMode.INTROSPECT); + }); + } + + private void assertTestTypeConstructorHint(Consumer constructorHint) { assertThat(this.reflectionHints.typeHints()).singleElement().satisfies(typeHint -> { assertThat(typeHint.getMemberCategories()).isEmpty(); assertThat(typeHint.getType().getCanonicalName()).isEqualTo(TestType.class.getCanonicalName()); assertThat(typeHint.fields()).isEmpty(); - assertThat(typeHint.constructors()).singleElement().satisfies(constructorHint -> { - assertThat(constructorHint.getParameterTypes()).isEmpty(); - assertThat(constructorHint.getModes()).containsOnly(ExecutableMode.INVOKE); - }); + assertThat(typeHint.constructors()).singleElement().satisfies(constructorHint); assertThat(typeHint.methods()).isEmpty(); assertThat(typeHint.getMemberCategories()).isEmpty(); }); @@ -156,15 +212,44 @@ class ReflectionHintsTests { Method method = ReflectionUtils.findMethod(TestType.class, "setName", String.class); assertThat(method).isNotNull(); this.reflectionHints.registerMethod(method); + assertTestTypeMethodHints(methodHint -> { + assertThat(methodHint.getName()).isEqualTo("setName"); + assertThat(methodHint.getParameterTypes()).containsOnly(TypeReference.of(String.class)); + assertThat(methodHint.getMode()).isEqualTo(ExecutableMode.INVOKE); + }); + } + + @Test + void registerMethodWithEmptyCustomizerAppliesConsistentDefault() { + Method method = ReflectionUtils.findMethod(TestType.class, "setName", String.class); + assertThat(method).isNotNull(); + this.reflectionHints.registerMethod(method, methodHint -> {}); + assertTestTypeMethodHints(methodHint -> { + assertThat(methodHint.getName()).isEqualTo("setName"); + assertThat(methodHint.getParameterTypes()).containsOnly(TypeReference.of(String.class)); + assertThat(methodHint.getMode()).isEqualTo(ExecutableMode.INVOKE); + }); + } + + @Test + void registerMethodWithCustomizerAppliesCustomization() { + Method method = ReflectionUtils.findMethod(TestType.class, "setName", String.class); + assertThat(method).isNotNull(); + this.reflectionHints.registerMethod(method, methodHint -> + methodHint.withMode(ExecutableMode.INTROSPECT)); + assertTestTypeMethodHints(methodHint -> { + assertThat(methodHint.getName()).isEqualTo("setName"); + assertThat(methodHint.getParameterTypes()).containsOnly(TypeReference.of(String.class)); + assertThat(methodHint.getMode()).isEqualTo(ExecutableMode.INTROSPECT); + }); + } + + private void assertTestTypeMethodHints(Consumer methodHint) { assertThat(this.reflectionHints.typeHints()).singleElement().satisfies(typeHint -> { assertThat(typeHint.getType().getCanonicalName()).isEqualTo(TestType.class.getCanonicalName()); assertThat(typeHint.fields()).isEmpty(); assertThat(typeHint.constructors()).isEmpty(); - assertThat(typeHint.methods()).singleElement().satisfies(methodHint -> { - assertThat(methodHint.getName()).isEqualTo("setName"); - assertThat(methodHint.getParameterTypes()).containsOnly(TypeReference.of(String.class)); - assertThat(methodHint.getModes()).containsOnly(ExecutableMode.INVOKE); - }); + assertThat(typeHint.methods()).singleElement().satisfies(methodHint); }); } diff --git a/spring-core/src/test/java/org/springframework/aot/hint/TypeHintTests.java b/spring-core/src/test/java/org/springframework/aot/hint/TypeHintTests.java index e781352c523..f66b83120c4 100644 --- a/spring-core/src/test/java/org/springframework/aot/hint/TypeHintTests.java +++ b/spring-core/src/test/java/org/springframework/aot/hint/TypeHintTests.java @@ -87,12 +87,25 @@ class TypeHintTests { constructorHint -> constructorHint.withMode(ExecutableMode.INVOKE)).build(); assertThat(hint.constructors()).singleElement().satisfies(constructorHint -> { assertThat(constructorHint.getParameterTypes()).containsOnlyOnceElementsOf(parameterTypes); - assertThat(constructorHint.getModes()).containsOnly(ExecutableMode.INVOKE); + assertThat(constructorHint.getMode()).isEqualTo(ExecutableMode.INVOKE); }); } @Test void createConstructorReuseBuilder() { + List parameterTypes = TypeReference.listOf(byte[].class, int.class); + Builder builder = TypeHint.of(TypeReference.of(String.class)).withConstructor(parameterTypes, + constructorHint -> constructorHint.withMode(ExecutableMode.INTROSPECT)); + TypeHint hint = builder.withConstructor(parameterTypes, constructorHint -> + constructorHint.withMode(ExecutableMode.INVOKE)).build(); + assertThat(hint.constructors()).singleElement().satisfies(constructorHint -> { + assertThat(constructorHint.getParameterTypes()).containsExactlyElementsOf(parameterTypes); + assertThat(constructorHint.getMode()).isEqualTo(ExecutableMode.INVOKE); + }); + } + + @Test + void createConstructorReuseBuilderAndApplyExecutableModePrecedence() { List parameterTypes = TypeReference.listOf(byte[].class, int.class); Builder builder = TypeHint.of(TypeReference.of(String.class)).withConstructor(parameterTypes, constructorHint -> constructorHint.withMode(ExecutableMode.INVOKE)); @@ -100,7 +113,7 @@ class TypeHintTests { constructorHint.withMode(ExecutableMode.INTROSPECT)).build(); assertThat(hint.constructors()).singleElement().satisfies(constructorHint -> { assertThat(constructorHint.getParameterTypes()).containsExactlyElementsOf(parameterTypes); - assertThat(constructorHint.getModes()).containsOnly(ExecutableMode.INVOKE, ExecutableMode.INTROSPECT); + assertThat(constructorHint.getMode()).isEqualTo(ExecutableMode.INVOKE); }); } @@ -112,7 +125,7 @@ class TypeHintTests { assertThat(hint.methods()).singleElement().satisfies(methodHint -> { assertThat(methodHint.getName()).isEqualTo("valueOf"); assertThat(methodHint.getParameterTypes()).containsExactlyElementsOf(parameterTypes); - assertThat(methodHint.getModes()).containsOnly(ExecutableMode.INVOKE); + assertThat(methodHint.getMode()).isEqualTo(ExecutableMode.INVOKE); }); } @@ -120,13 +133,27 @@ class TypeHintTests { void createWithMethodReuseBuilder() { List parameterTypes = TypeReference.listOf(char[].class); Builder builder = TypeHint.of(TypeReference.of(String.class)).withMethod("valueOf", parameterTypes, - methodHint -> methodHint.withMode(ExecutableMode.INVOKE)); + methodHint -> methodHint.withMode(ExecutableMode.INTROSPECT)); TypeHint hint = builder.withMethod("valueOf", parameterTypes, - methodHint -> methodHint.setModes(ExecutableMode.INTROSPECT)).build(); + methodHint -> methodHint.withMode(ExecutableMode.INVOKE)).build(); assertThat(hint.methods()).singleElement().satisfies(methodHint -> { assertThat(methodHint.getName()).isEqualTo("valueOf"); assertThat(methodHint.getParameterTypes()).containsExactlyElementsOf(parameterTypes); - assertThat(methodHint.getModes()).containsOnly(ExecutableMode.INTROSPECT); + assertThat(methodHint.getMode()).isEqualTo(ExecutableMode.INVOKE); + }); + } + + @Test + void createWithMethodReuseBuilderAndApplyExecutableModePrecedence() { + List parameterTypes = TypeReference.listOf(char[].class); + Builder builder = TypeHint.of(TypeReference.of(String.class)).withMethod("valueOf", parameterTypes, + methodHint -> methodHint.withMode(ExecutableMode.INVOKE)); + TypeHint hint = builder.withMethod("valueOf", parameterTypes, + methodHint -> methodHint.withMode(ExecutableMode.INTROSPECT)).build(); + assertThat(hint.methods()).singleElement().satisfies(methodHint -> { + assertThat(methodHint.getName()).isEqualTo("valueOf"); + assertThat(methodHint.getParameterTypes()).containsExactlyElementsOf(parameterTypes); + assertThat(methodHint.getMode()).isEqualTo(ExecutableMode.INVOKE); }); } diff --git a/spring-core/src/test/java/org/springframework/aot/hint/annotation/SimpleReflectiveProcessorTests.java b/spring-core/src/test/java/org/springframework/aot/hint/annotation/SimpleReflectiveProcessorTests.java index 4ab2c603d88..45bf6f7c9d7 100644 --- a/spring-core/src/test/java/org/springframework/aot/hint/annotation/SimpleReflectiveProcessorTests.java +++ b/spring-core/src/test/java/org/springframework/aot/hint/annotation/SimpleReflectiveProcessorTests.java @@ -60,7 +60,7 @@ class SimpleReflectiveProcessorTests { assertThat(typeHint.getMemberCategories()).isEmpty(); assertThat(typeHint.constructors()).singleElement().satisfies(constructorHint -> { assertThat(constructorHint.getName()).isEqualTo(""); - assertThat(constructorHint.getModes()).containsExactly(ExecutableMode.INVOKE); + assertThat(constructorHint.getMode()).isEqualTo(ExecutableMode.INVOKE); assertThat(constructorHint.getParameterTypes()).containsExactly(TypeReference.of(String.class)); }); assertThat(typeHint.fields()).isEmpty(); @@ -93,7 +93,7 @@ class SimpleReflectiveProcessorTests { assertThat(typeHint.fields()).isEmpty(); assertThat(typeHint.methods()).singleElement().satisfies(methodHint -> { assertThat(methodHint.getName()).isEqualTo("setName"); - assertThat(methodHint.getModes()).containsExactly(ExecutableMode.INVOKE); + assertThat(methodHint.getMode()).isEqualTo(ExecutableMode.INVOKE); assertThat(methodHint.getParameterTypes()).containsExactly(TypeReference.of(String.class)); }); }); diff --git a/spring-core/src/test/java/org/springframework/aot/hint/predicate/ReflectionHintsPredicatesTests.java b/spring-core/src/test/java/org/springframework/aot/hint/predicate/ReflectionHintsPredicatesTests.java index d3390fe15b4..3706197d509 100644 --- a/spring-core/src/test/java/org/springframework/aot/hint/predicate/ReflectionHintsPredicatesTests.java +++ b/spring-core/src/test/java/org/springframework/aot/hint/predicate/ReflectionHintsPredicatesTests.java @@ -192,7 +192,8 @@ class ReflectionHintsPredicatesTests { @Test void constructorInvocationDoesNotMatchConstructorHint() { runtimeHints.reflection().registerType(SampleClass.class, typeHint -> typeHint. - withConstructor(Collections.emptyList(), constructorHint -> {})); + withConstructor(Collections.emptyList(), constructorHint -> + constructorHint.withMode(ExecutableMode.INTROSPECT))); assertPredicateDoesNotMatch(reflection.onConstructor(publicConstructor).invoke()); } @@ -270,7 +271,8 @@ class ReflectionHintsPredicatesTests { @Test void privateConstructorInvocationDoesNotMatchConstructorHint() { runtimeHints.reflection().registerType(SampleClass.class, typeHint -> - typeHint.withConstructor(TypeReference.listOf(String.class), constructorHint -> {})); + typeHint.withConstructor(TypeReference.listOf(String.class), constructorHint -> + constructorHint.withMode(ExecutableMode.INTROSPECT))); assertPredicateDoesNotMatch(reflection.onConstructor(privateConstructor).invoke()); } @@ -348,14 +350,15 @@ class ReflectionHintsPredicatesTests { @Test void methodInvocationDoesNotMatchMethodHint() { - runtimeHints.reflection().registerType(SampleClass.class, typeHint -> typeHint.withMethod("publicMethod", Collections.emptyList(), methodHint -> { - })); + runtimeHints.reflection().registerType(SampleClass.class, typeHint -> typeHint.withMethod("publicMethod", Collections.emptyList(), + methodHint -> methodHint.withMode(ExecutableMode.INTROSPECT))); assertPredicateDoesNotMatch(reflection.onMethod(SampleClass.class, "publicMethod").invoke()); } @Test void methodInvocationMatchesMethodInvocationHint() { - runtimeHints.reflection().registerType(SampleClass.class, typeHint -> typeHint.withMethod("publicMethod", Collections.emptyList(), methodHint -> methodHint.withMode(ExecutableMode.INVOKE))); + runtimeHints.reflection().registerType(SampleClass.class, typeHint -> typeHint.withMethod("publicMethod", Collections.emptyList(), + methodHint -> methodHint.withMode(ExecutableMode.INVOKE))); assertPredicateMatches(reflection.onMethod(SampleClass.class, "publicMethod").invoke()); } @@ -416,8 +419,8 @@ class ReflectionHintsPredicatesTests { @Test void privateMethodInvocationDoesNotMatchMethodHint() { - runtimeHints.reflection().registerType(SampleClass.class, typeHint -> typeHint.withMethod("privateMethod", Collections.emptyList(), methodHint -> { - })); + runtimeHints.reflection().registerType(SampleClass.class, typeHint -> typeHint.withMethod("privateMethod", Collections.emptyList(), + methodHint -> methodHint.withMode(ExecutableMode.INTROSPECT))); assertPredicateDoesNotMatch(reflection.onMethod(SampleClass.class, "privateMethod").invoke()); } @@ -470,8 +473,8 @@ class ReflectionHintsPredicatesTests { @Test void fieldWriteReflectionDoesNotMatchFieldHint() { - runtimeHints.reflection().registerType(SampleClass.class, typeHint -> typeHint.withField("publicField", fieldHint -> { - })); + runtimeHints.reflection().registerType(SampleClass.class, typeHint -> typeHint.withField("publicField", + fieldHint -> fieldHint.allowWrite(false))); assertPredicateDoesNotMatch(reflection.onField(SampleClass.class, "publicField").allowWrite()); } diff --git a/spring-core/src/test/java/org/springframework/aot/nativex/FileNativeConfigurationWriterTests.java b/spring-core/src/test/java/org/springframework/aot/nativex/FileNativeConfigurationWriterTests.java index dc77407689a..121e75c9ebf 100644 --- a/spring-core/src/test/java/org/springframework/aot/nativex/FileNativeConfigurationWriterTests.java +++ b/spring-core/src/test/java/org/springframework/aot/nativex/FileNativeConfigurationWriterTests.java @@ -106,7 +106,7 @@ public class FileNativeConfigurationWriterTests { MemberCategory.INTROSPECT_PUBLIC_METHODS, MemberCategory.INTROSPECT_DECLARED_METHODS, MemberCategory.INVOKE_PUBLIC_METHODS, MemberCategory.INVOKE_DECLARED_METHODS, MemberCategory.PUBLIC_CLASSES, MemberCategory.DECLARED_CLASSES) - .withField("DEFAULT_CHARSET", fieldBuilder -> {}) + .withField("DEFAULT_CHARSET", fieldBuilder -> fieldBuilder.allowWrite(false)) .withField("defaultCharset", fieldBuilder -> { fieldBuilder.allowWrite(true); fieldBuilder.allowUnsafeAccess(true); diff --git a/spring-core/src/test/java/org/springframework/aot/nativex/ReflectionHintsWriterTests.java b/spring-core/src/test/java/org/springframework/aot/nativex/ReflectionHintsWriterTests.java index 9060f5b0afa..e3c547a79b2 100644 --- a/spring-core/src/test/java/org/springframework/aot/nativex/ReflectionHintsWriterTests.java +++ b/spring-core/src/test/java/org/springframework/aot/nativex/ReflectionHintsWriterTests.java @@ -58,7 +58,7 @@ public class ReflectionHintsWriterTests { MemberCategory.INTROSPECT_PUBLIC_METHODS, MemberCategory.INTROSPECT_DECLARED_METHODS, MemberCategory.INVOKE_PUBLIC_METHODS, MemberCategory.INVOKE_DECLARED_METHODS, MemberCategory.PUBLIC_CLASSES, MemberCategory.DECLARED_CLASSES) - .withField("DEFAULT_CHARSET", fieldBuilder -> {}) + .withField("DEFAULT_CHARSET", fieldBuilder -> fieldBuilder.allowWrite(false)) .withField("defaultCharset", fieldBuilder -> { fieldBuilder.allowWrite(true); fieldBuilder.allowUnsafeAccess(true); @@ -185,7 +185,7 @@ public class ReflectionHintsWriterTests { hints.registerType(Integer.class, builder -> builder.withMethod("parseInt", TypeReference.listOf(String.class), b -> b.withMode(ExecutableMode.INVOKE))); hints.registerType(Integer.class, builder -> builder.withMethod("parseInt", - TypeReference.listOf(String.class), b -> b.withMode(ExecutableMode.INTROSPECT))); + TypeReference.listOf(String.class, int.class), b -> b.withMode(ExecutableMode.INTROSPECT))); assertEquals(""" [ @@ -194,7 +194,7 @@ public class ReflectionHintsWriterTests { "queriedMethods": [ { "name": "parseInt", - "parameterTypes": ["java.lang.String"] + "parameterTypes": ["java.lang.String", "int"] } ], "methods": [ diff --git a/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/MessageMappingReflectiveProcessor.java b/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/MessageMappingReflectiveProcessor.java index f1004dc6ba3..4f7ac3942f3 100644 --- a/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/MessageMappingReflectiveProcessor.java +++ b/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/MessageMappingReflectiveProcessor.java @@ -64,13 +64,13 @@ class MessageMappingReflectiveProcessor implements ReflectiveProcessor { } protected void registerMethodHints(ReflectionHints hints, Method method) { - hints.registerMethod(method, hint -> hint.setModes(ExecutableMode.INVOKE)); + hints.registerMethod(method, hint -> hint.withMode(ExecutableMode.INVOKE)); registerParameterHints(hints, method); registerReturnValueHints(hints, method); } protected void registerParameterHints(ReflectionHints hints, Method method) { - hints.registerMethod(method, hint -> hint.setModes(ExecutableMode.INVOKE)); + hints.registerMethod(method, hint -> hint.withMode(ExecutableMode.INVOKE)); for (Parameter parameter : method.getParameters()) { MethodParameter methodParameter = MethodParameter.forParameter(parameter); if (Message.class.isAssignableFrom(methodParameter.getParameterType())) { diff --git a/spring-web/src/main/java/org/springframework/web/bind/annotation/RequestMappingReflectiveProcessor.java b/spring-web/src/main/java/org/springframework/web/bind/annotation/RequestMappingReflectiveProcessor.java index c1661d39f63..d61868aeb0e 100644 --- a/spring-web/src/main/java/org/springframework/web/bind/annotation/RequestMappingReflectiveProcessor.java +++ b/spring-web/src/main/java/org/springframework/web/bind/annotation/RequestMappingReflectiveProcessor.java @@ -63,13 +63,13 @@ class RequestMappingReflectiveProcessor implements ReflectiveProcessor { } protected void registerMethodHints(ReflectionHints hints, Method method) { - hints.registerMethod(method, hint -> hint.setModes(ExecutableMode.INVOKE)); + hints.registerMethod(method, hint -> hint.withMode(ExecutableMode.INVOKE)); registerParameterHints(hints, method); registerReturnValueHints(hints, method); } protected void registerParameterHints(ReflectionHints hints, Method method) { - hints.registerMethod(method, hint -> hint.setModes(ExecutableMode.INVOKE)); + hints.registerMethod(method, hint -> hint.withMode(ExecutableMode.INVOKE)); for (Parameter parameter : method.getParameters()) { MethodParameter methodParameter = MethodParameter.forParameter(parameter); if (methodParameter.hasParameterAnnotation(RequestBody.class) || From 5aa858315947867f5b5caba34834d4c6c3d0ffa3 Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Wed, 24 Aug 2022 15:44:54 +0200 Subject: [PATCH 2/3] Add shortcut to register an executable with a mode See gh-29011 --- .../AutowiredAnnotationBeanPostProcessor.java | 6 +---- ...BeanDefinitionPropertiesCodeGenerator.java | 6 +---- .../aot/InstanceSupplierCodeGenerator.java | 8 ++---- .../aot/BindingReflectionHintsRegistrar.java | 9 ++----- .../aot/hint/ReflectionHints.java | 26 +++++++++++++++++-- .../aot/hint/ReflectionHintsTests.java | 22 ++++++++++++++++ .../MessageMappingReflectiveProcessor.java | 4 +-- .../RequestMappingReflectiveProcessor.java | 4 +-- 8 files changed, 56 insertions(+), 29 deletions(-) diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java index 9df071e8097..a1cff64cbb1 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java @@ -46,7 +46,6 @@ import org.springframework.aot.generate.GeneratedClass; import org.springframework.aot.generate.GeneratedMethod; import org.springframework.aot.generate.GenerationContext; import org.springframework.aot.generate.MethodReference; -import org.springframework.aot.hint.ExecutableHint; import org.springframework.aot.hint.ExecutableMode; import org.springframework.aot.hint.FieldHint; import org.springframework.aot.hint.RuntimeHints; @@ -914,9 +913,6 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA private static final String INSTANCE_PARAMETER = "instance"; - private static final Consumer INTROSPECT = builder -> builder - .withMode(ExecutableMode.INTROSPECT); - private static final Consumer ALLOW_WRITE = builder -> builder .allowWrite(true); @@ -1024,7 +1020,7 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA INSTANCE_PARAMETER); } else { - hints.reflection().registerMethod(method, INTROSPECT); + hints.reflection().registerMethod(method, ExecutableMode.INTROSPECT); CodeBlock arguments = new AutowiredArgumentsCodeGenerator(this.target, method).generateCode(method.getParameterTypes()); CodeBlock injectionCode = CodeBlock.of("args -> $L.$L($L)", diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanDefinitionPropertiesCodeGenerator.java b/spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanDefinitionPropertiesCodeGenerator.java index a85646d1f9d..39380e784d3 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanDefinitionPropertiesCodeGenerator.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanDefinitionPropertiesCodeGenerator.java @@ -28,12 +28,10 @@ import java.util.Map; import java.util.Objects; import java.util.function.BiFunction; import java.util.function.BiPredicate; -import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Predicate; import org.springframework.aot.generate.GeneratedMethods; -import org.springframework.aot.hint.ExecutableHint; import org.springframework.aot.hint.ExecutableMode; import org.springframework.aot.hint.RuntimeHints; import org.springframework.beans.BeanInfoFactory; @@ -83,8 +81,6 @@ class BeanDefinitionPropertiesCodeGenerator { private static final String BEAN_DEFINITION_VARIABLE = BeanRegistrationCodeFragments.BEAN_DEFINITION_VARIABLE; - private static final Consumer INVOKE_HINT = hint -> hint.withMode(ExecutableMode.INVOKE); - private static final BeanInfoFactory beanInfoFactory = new ExtendedBeanInfoFactory(); private final RuntimeHints hints; @@ -195,7 +191,7 @@ class BeanDefinitionPropertiesCodeGenerator { for (PropertyValue propertyValue : propertyValues) { Method writeMethod = writeMethods.get(propertyValue.getName()); if (writeMethod != null) { - this.hints.reflection().registerMethod(writeMethod, INVOKE_HINT); + this.hints.reflection().registerMethod(writeMethod, ExecutableMode.INVOKE); } } } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/aot/InstanceSupplierCodeGenerator.java b/spring-beans/src/main/java/org/springframework/beans/factory/aot/InstanceSupplierCodeGenerator.java index 1728e41ee3f..66bba162047 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/aot/InstanceSupplierCodeGenerator.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/aot/InstanceSupplierCodeGenerator.java @@ -28,7 +28,6 @@ import org.springframework.aot.generate.AccessVisibility; import org.springframework.aot.generate.GeneratedMethod; import org.springframework.aot.generate.GeneratedMethods; import org.springframework.aot.generate.GenerationContext; -import org.springframework.aot.hint.ExecutableHint; import org.springframework.aot.hint.ExecutableMode; import org.springframework.beans.factory.support.InstanceSupplier; import org.springframework.beans.factory.support.RegisteredBean; @@ -69,9 +68,6 @@ class InstanceSupplierCodeGenerator { private static final CodeBlock NO_ARGS = CodeBlock.of(""); - private static final Consumer INTROSPECT = hint -> hint - .withMode(ExecutableMode.INTROSPECT); - private final GenerationContext generationContext; @@ -128,7 +124,7 @@ class InstanceSupplierCodeGenerator { Constructor constructor, boolean dependsOnBean, Class declaringClass) { this.generationContext.getRuntimeHints().reflection() - .registerConstructor(constructor, INTROSPECT); + .registerConstructor(constructor, ExecutableMode.INTROSPECT); if (!dependsOnBean && constructor.getParameterCount() == 0) { if (!this.allowDirectSupplierShortcut) { return CodeBlock.of("$T.using($T::new)", InstanceSupplier.class, @@ -225,7 +221,7 @@ class InstanceSupplierCodeGenerator { Class beanClass, Method factoryMethod, Class declaringClass, boolean dependsOnBean) { this.generationContext.getRuntimeHints().reflection() - .registerMethod(factoryMethod, INTROSPECT); + .registerMethod(factoryMethod, ExecutableMode.INTROSPECT); if (!dependsOnBean && factoryMethod.getParameterCount() == 0) { CodeBlock.Builder code = CodeBlock.builder(); code.add("$T.<$T>forFactoryMethod($T.class, $S)", BeanInstanceSupplier.class, diff --git a/spring-context/src/main/java/org/springframework/context/aot/BindingReflectionHintsRegistrar.java b/spring-context/src/main/java/org/springframework/context/aot/BindingReflectionHintsRegistrar.java index ad0af7827ed..c75612dbecb 100644 --- a/spring-context/src/main/java/org/springframework/context/aot/BindingReflectionHintsRegistrar.java +++ b/spring-context/src/main/java/org/springframework/context/aot/BindingReflectionHintsRegistrar.java @@ -25,12 +25,10 @@ import java.lang.reflect.RecordComponent; import java.lang.reflect.Type; import java.util.LinkedHashSet; import java.util.Set; -import java.util.function.Consumer; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.springframework.aot.hint.ExecutableHint; import org.springframework.aot.hint.ExecutableMode; import org.springframework.aot.hint.MemberCategory; import org.springframework.aot.hint.ReflectionHints; @@ -54,9 +52,6 @@ public class BindingReflectionHintsRegistrar { private static final Log logger = LogFactory.getLog(BindingReflectionHintsRegistrar.class); - private static final Consumer INVOKE = builder -> builder - .withMode(ExecutableMode.INVOKE); - private static final String KOTLIN_COMPANION_SUFFIX = "$Companion"; /** @@ -127,7 +122,7 @@ public class BindingReflectionHintsRegistrar { } private void registerRecordHints(ReflectionHints hints, Set seen, Method method) { - hints.registerMethod(method, INVOKE); + hints.registerMethod(method, ExecutableMode.INVOKE); MethodParameter methodParameter = MethodParameter.forExecutable(method, -1); Type methodParameterType = methodParameter.getGenericParameterType(); if (!seen.contains(methodParameterType)) { @@ -138,7 +133,7 @@ public class BindingReflectionHintsRegistrar { private void registerPropertyHints(ReflectionHints hints, Set seen, @Nullable Method method, int parameterIndex) { if (method != null && method.getDeclaringClass() != Object.class && method.getDeclaringClass() != Enum.class) { - hints.registerMethod(method, INVOKE); + hints.registerMethod(method, ExecutableMode.INVOKE); MethodParameter methodParameter = MethodParameter.forExecutable(method, parameterIndex); Type methodParameterType = methodParameter.getGenericParameterType(); if (!seen.contains(methodParameterType)) { diff --git a/spring-core/src/main/java/org/springframework/aot/hint/ReflectionHints.java b/spring-core/src/main/java/org/springframework/aot/hint/ReflectionHints.java index b9460ed01bf..a30046c2cf8 100644 --- a/spring-core/src/main/java/org/springframework/aot/hint/ReflectionHints.java +++ b/spring-core/src/main/java/org/springframework/aot/hint/ReflectionHints.java @@ -158,6 +158,17 @@ public class ReflectionHints { typeHint -> typeHint.withConstructor(mapParameters(constructor), constructorHint)); } + /** + * Register the need for reflection on the specified {@link Constructor}, + * using the specified {@link ExecutableMode}. + * @param constructor the constructor that requires reflection + * @param mode the requested mode + * @return {@code this}, to facilitate method chaining + */ + public ReflectionHints registerConstructor(Constructor constructor, ExecutableMode mode) { + return registerConstructor(constructor, constructorHint -> constructorHint.withMode(mode)); + } + /** * Register the need for reflection on the specified {@link Constructor}, * enabling {@link ExecutableMode#INVOKE}. @@ -165,7 +176,7 @@ public class ReflectionHints { * @return {@code this}, to facilitate method chaining */ public ReflectionHints registerConstructor(Constructor constructor) { - return registerConstructor(constructor, constructorHint -> constructorHint.withMode(ExecutableMode.INVOKE)); + return registerConstructor(constructor, ExecutableMode.INVOKE); } /** @@ -179,6 +190,17 @@ public class ReflectionHints { typeHint -> typeHint.withMethod(method.getName(), mapParameters(method), methodHint)); } + /** + * Register the need for reflection on the specified {@link Method}, + * using the specified {@link ExecutableMode}. + * @param method the method that requires reflection + * @param mode the requested mode + * @return {@code this}, to facilitate method chaining + */ + public ReflectionHints registerMethod(Method method, ExecutableMode mode) { + return registerMethod(method, methodHint -> methodHint.withMode(mode)); + } + /** * Register the need for reflection on the specified {@link Method}, * enabling {@link ExecutableMode#INVOKE}. @@ -186,7 +208,7 @@ public class ReflectionHints { * @return {@code this}, to facilitate method chaining */ public ReflectionHints registerMethod(Method method) { - return registerMethod(method, methodHint -> methodHint.withMode(ExecutableMode.INVOKE)); + return registerMethod(method, ExecutableMode.INVOKE); } private List mapParameters(Executable executable) { diff --git a/spring-core/src/test/java/org/springframework/aot/hint/ReflectionHintsTests.java b/spring-core/src/test/java/org/springframework/aot/hint/ReflectionHintsTests.java index 24799c99da8..8ab8d7180d1 100644 --- a/spring-core/src/test/java/org/springframework/aot/hint/ReflectionHintsTests.java +++ b/spring-core/src/test/java/org/springframework/aot/hint/ReflectionHintsTests.java @@ -176,6 +176,16 @@ class ReflectionHintsTests { }); } + @Test + void registerConstructorWithMode() { + this.reflectionHints.registerConstructor( + TestType.class.getDeclaredConstructors()[0], ExecutableMode.INTROSPECT); + assertTestTypeConstructorHint(constructorHint -> { + assertThat(constructorHint.getParameterTypes()).isEmpty(); + assertThat(constructorHint.getMode()).isEqualTo(ExecutableMode.INTROSPECT); + }); + } + @Test void registerConstructorWithEmptyCustomizerAppliesConsistentDefault() { this.reflectionHints.registerConstructor(TestType.class.getDeclaredConstructors()[0], @@ -219,6 +229,18 @@ class ReflectionHintsTests { }); } + @Test + void registerMethodWithMode() { + Method method = ReflectionUtils.findMethod(TestType.class, "setName", String.class); + assertThat(method).isNotNull(); + this.reflectionHints.registerMethod(method, ExecutableMode.INTROSPECT); + assertTestTypeMethodHints(methodHint -> { + assertThat(methodHint.getName()).isEqualTo("setName"); + assertThat(methodHint.getParameterTypes()).containsOnly(TypeReference.of(String.class)); + assertThat(methodHint.getMode()).isEqualTo(ExecutableMode.INTROSPECT); + }); + } + @Test void registerMethodWithEmptyCustomizerAppliesConsistentDefault() { Method method = ReflectionUtils.findMethod(TestType.class, "setName", String.class); diff --git a/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/MessageMappingReflectiveProcessor.java b/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/MessageMappingReflectiveProcessor.java index 4f7ac3942f3..8fa0be9f606 100644 --- a/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/MessageMappingReflectiveProcessor.java +++ b/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/MessageMappingReflectiveProcessor.java @@ -64,13 +64,13 @@ class MessageMappingReflectiveProcessor implements ReflectiveProcessor { } protected void registerMethodHints(ReflectionHints hints, Method method) { - hints.registerMethod(method, hint -> hint.withMode(ExecutableMode.INVOKE)); + hints.registerMethod(method, ExecutableMode.INVOKE); registerParameterHints(hints, method); registerReturnValueHints(hints, method); } protected void registerParameterHints(ReflectionHints hints, Method method) { - hints.registerMethod(method, hint -> hint.withMode(ExecutableMode.INVOKE)); + hints.registerMethod(method, ExecutableMode.INVOKE); for (Parameter parameter : method.getParameters()) { MethodParameter methodParameter = MethodParameter.forParameter(parameter); if (Message.class.isAssignableFrom(methodParameter.getParameterType())) { diff --git a/spring-web/src/main/java/org/springframework/web/bind/annotation/RequestMappingReflectiveProcessor.java b/spring-web/src/main/java/org/springframework/web/bind/annotation/RequestMappingReflectiveProcessor.java index d61868aeb0e..ac45533aee3 100644 --- a/spring-web/src/main/java/org/springframework/web/bind/annotation/RequestMappingReflectiveProcessor.java +++ b/spring-web/src/main/java/org/springframework/web/bind/annotation/RequestMappingReflectiveProcessor.java @@ -63,13 +63,13 @@ class RequestMappingReflectiveProcessor implements ReflectiveProcessor { } protected void registerMethodHints(ReflectionHints hints, Method method) { - hints.registerMethod(method, hint -> hint.withMode(ExecutableMode.INVOKE)); + hints.registerMethod(method, ExecutableMode.INVOKE); registerParameterHints(hints, method); registerReturnValueHints(hints, method); } protected void registerParameterHints(ReflectionHints hints, Method method) { - hints.registerMethod(method, hint -> hint.withMode(ExecutableMode.INVOKE)); + hints.registerMethod(method, ExecutableMode.INVOKE); for (Parameter parameter : method.getParameters()) { MethodParameter methodParameter = MethodParameter.forParameter(parameter); if (methodParameter.hasParameterAnnotation(RequestBody.class) || From 7ca57b7e8085196ccb7c5ed9366e90d46835fa75 Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Wed, 24 Aug 2022 16:51:42 +0200 Subject: [PATCH 3/3] Use consistent registration API in TypeHint.Builder This commit adapts the registration of fields, constructors, and methods to provide the same convenience than the reflection-based one available in ReflectionHints. See gh-29011 --- .../aot/agent/InstrumentedMethodTests.java | 32 ++-- .../springframework/aot/hint/TypeHint.java | 54 ++++++ .../aot/hint/TypeHintTests.java | 167 ++++++++++++++---- .../ReflectionHintsPredicatesTests.java | 39 ++-- .../FileNativeConfigurationWriterTests.java | 8 +- .../nativex/ReflectionHintsWriterTests.java | 46 +++-- 6 files changed, 249 insertions(+), 97 deletions(-) diff --git a/spring-core-test/src/test/java/org/springframework/aot/agent/InstrumentedMethodTests.java b/spring-core-test/src/test/java/org/springframework/aot/agent/InstrumentedMethodTests.java index b6f09e3d118..d5a063e3b8d 100644 --- a/spring-core-test/src/test/java/org/springframework/aot/agent/InstrumentedMethodTests.java +++ b/spring-core-test/src/test/java/org/springframework/aot/agent/InstrumentedMethodTests.java @@ -151,15 +151,15 @@ class InstrumentedMethodTests { @Test void classGetConstructorShouldMatchInstrospectConstructorHint() { - hints.reflection().registerType(String.class, typeHint -> typeHint.withConstructor(Collections.emptyList(), - constructorHint -> constructorHint.withMode(ExecutableMode.INTROSPECT))); + hints.reflection().registerType(String.class,typeHint -> + typeHint.withConstructor(Collections.emptyList(), ExecutableMode.INTROSPECT)); assertThatInvocationMatches(InstrumentedMethod.CLASS_GETCONSTRUCTOR, this.stringGetConstructor); } @Test void classGetConstructorShouldMatchInvokeConstructorHint() { - hints.reflection().registerType(String.class, typeHint -> typeHint.withConstructor(Collections.emptyList(), - constructorHint -> constructorHint.withMode(ExecutableMode.INVOKE))); + hints.reflection().registerType(String.class, typeHint -> + typeHint.withConstructor(Collections.emptyList(),ExecutableMode.INVOKE)); assertThatInvocationMatches(InstrumentedMethod.CLASS_GETCONSTRUCTOR, this.stringGetConstructor); } @@ -201,15 +201,15 @@ class InstrumentedMethodTests { @Test void classGetDeclaredConstructorShouldMatchInstrospectConstructorHint() { - hints.reflection().registerType(String.class, typeHint -> typeHint.withConstructor(TypeReference.listOf(byte[].class, byte.class), - constructorHint -> constructorHint.withMode(ExecutableMode.INTROSPECT))); + hints.reflection().registerType(String.class, typeHint -> + typeHint.withConstructor(TypeReference.listOf(byte[].class, byte.class), ExecutableMode.INTROSPECT)); assertThatInvocationMatches(InstrumentedMethod.CLASS_GETDECLAREDCONSTRUCTOR, this.stringGetDeclaredConstructor); } @Test void classGetDeclaredConstructorShouldMatchInvokeConstructorHint() { - hints.reflection().registerType(String.class, typeHint -> typeHint.withConstructor(TypeReference.listOf(byte[].class, byte.class), - constructorHint -> constructorHint.withMode(ExecutableMode.INVOKE))); + hints.reflection().registerType(String.class, typeHint -> + typeHint.withConstructor(TypeReference.listOf(byte[].class, byte.class), ExecutableMode.INVOKE)); assertThatInvocationMatches(InstrumentedMethod.CLASS_GETDECLAREDCONSTRUCTOR, this.stringGetDeclaredConstructor); } @@ -237,7 +237,7 @@ class InstrumentedMethodTests { RecordedInvocation invocation = RecordedInvocation.of(InstrumentedMethod.CONSTRUCTOR_NEWINSTANCE) .onInstance(String.class.getConstructor()).returnValue("").build(); hints.reflection().registerType(String.class, typeHint -> - typeHint.withConstructor(Collections.emptyList(), constructorHint -> constructorHint.withMode(ExecutableMode.INVOKE))); + typeHint.withConstructor(Collections.emptyList(), ExecutableMode.INVOKE)); assertThatInvocationMatches(InstrumentedMethod.CONSTRUCTOR_NEWINSTANCE, invocation); } @@ -246,7 +246,7 @@ class InstrumentedMethodTests { RecordedInvocation invocation = RecordedInvocation.of(InstrumentedMethod.CONSTRUCTOR_NEWINSTANCE) .onInstance(String.class.getConstructor()).returnValue("").build(); hints.reflection().registerType(String.class, typeHint -> - typeHint.withConstructor(Collections.emptyList(), constructorHint -> constructorHint.withMode(ExecutableMode.INTROSPECT))); + typeHint.withConstructor(Collections.emptyList(), ExecutableMode.INTROSPECT)); assertThatInvocationDoesNotMatch(InstrumentedMethod.CONSTRUCTOR_NEWINSTANCE, invocation); } @@ -285,7 +285,7 @@ class InstrumentedMethodTests { void classGetDeclaredMethodShouldMatchIntrospectMethodHint() { List parameterTypes = TypeReference.listOf(int.class, float.class); hints.reflection().registerType(String.class, typeHint -> - typeHint.withMethod("scale", parameterTypes, methodHint -> methodHint.withMode(ExecutableMode.INTROSPECT))); + typeHint.withMethod("scale", parameterTypes, ExecutableMode.INTROSPECT)); assertThatInvocationMatches(InstrumentedMethod.CLASS_GETDECLAREDMETHOD, this.stringGetScaleMethod); } @@ -293,7 +293,7 @@ class InstrumentedMethodTests { void classGetDeclaredMethodShouldMatchInvokeMethodHint() { List parameterTypes = TypeReference.listOf(int.class, float.class); hints.reflection().registerType(String.class, typeHint -> - typeHint.withMethod("scale", parameterTypes, methodHint -> methodHint.withMode(ExecutableMode.INVOKE))); + typeHint.withMethod("scale", parameterTypes, ExecutableMode.INVOKE)); assertThatInvocationMatches(InstrumentedMethod.CLASS_GETDECLAREDMETHOD, this.stringGetScaleMethod); } @@ -378,14 +378,14 @@ class InstrumentedMethodTests { @Test void classGetMethodShouldMatchIntrospectMethodHint() { hints.reflection().registerType(String.class, typeHint -> - typeHint.withMethod("toString", Collections.emptyList(), methodHint -> methodHint.withMode(ExecutableMode.INTROSPECT))); + typeHint.withMethod("toString", Collections.emptyList(), ExecutableMode.INTROSPECT)); assertThatInvocationMatches(InstrumentedMethod.CLASS_GETMETHOD, this.stringGetToStringMethod); } @Test void classGetMethodShouldMatchInvokeMethodHint() { hints.reflection().registerType(String.class, typeHint -> - typeHint.withMethod("toString", Collections.emptyList(), methodHint -> methodHint.withMode(ExecutableMode.INVOKE))); + typeHint.withMethod("toString", Collections.emptyList(), ExecutableMode.INVOKE)); assertThatInvocationMatches(InstrumentedMethod.CLASS_GETMETHOD, this.stringGetToStringMethod); } @@ -412,7 +412,7 @@ class InstrumentedMethodTests { RecordedInvocation invocation = RecordedInvocation.of(InstrumentedMethod.METHOD_INVOKE) .onInstance(String.class.getMethod("startsWith", String.class)).withArguments("testString", new Object[] { "test" }).build(); hints.reflection().registerType(String.class, typeHint -> typeHint.withMethod("startsWith", - TypeReference.listOf(String.class), methodHint -> methodHint.withMode(ExecutableMode.INVOKE))); + TypeReference.listOf(String.class), ExecutableMode.INVOKE)); assertThatInvocationMatches(InstrumentedMethod.METHOD_INVOKE, invocation); } @@ -421,7 +421,7 @@ class InstrumentedMethodTests { RecordedInvocation invocation = RecordedInvocation.of(InstrumentedMethod.METHOD_INVOKE) .onInstance(String.class.getMethod("toString")).withArguments("", new Object[0]).build(); hints.reflection().registerType(String.class, typeHint -> - typeHint.withMethod("toString", Collections.emptyList(), methodHint -> methodHint.withMode(ExecutableMode.INTROSPECT))); + typeHint.withMethod("toString", Collections.emptyList(), ExecutableMode.INTROSPECT)); assertThatInvocationDoesNotMatch(InstrumentedMethod.METHOD_INVOKE, invocation); } diff --git a/spring-core/src/main/java/org/springframework/aot/hint/TypeHint.java b/spring-core/src/main/java/org/springframework/aot/hint/TypeHint.java index 519eba94a68..e7781caabdf 100644 --- a/spring-core/src/main/java/org/springframework/aot/hint/TypeHint.java +++ b/spring-core/src/main/java/org/springframework/aot/hint/TypeHint.java @@ -173,6 +173,16 @@ public final class TypeHint implements ConditionalHint { return this; } + /** + * Register the need for reflection on the field with the specified name, + * enabling write access. + * @param name the name of the field + * @return {@code this}, to facilitate method chaining + */ + public Builder withField(String name) { + return withField(name, fieldHint -> {}); + } + /** * Register the need for reflection on the constructor with the specified * parameter types. @@ -189,6 +199,27 @@ public final class TypeHint implements ConditionalHint { return this; } + /** + * Register the need for reflection on the constructor with the specified + * parameter types, using the specified {@link ExecutableMode}. + * @param parameterTypes the parameter types of the constructor + * @param mode the requested mode + * @return {@code this}, to facilitate method chaining + */ + public Builder withConstructor(List parameterTypes, ExecutableMode mode) { + return withConstructor(parameterTypes, constructorHint -> constructorHint.withMode(mode)); + } + + /** + * Register the need for reflection on the constructor with the specified + * parameter types, enabling {@link ExecutableMode#INVOKE}. + * @param parameterTypes the parameter types of the constructor + * @return {@code this}, to facilitate method chaining + */ + public Builder withConstructor(List parameterTypes) { + return withConstructor(parameterTypes, ExecutableMode.INVOKE); + } + /** * Register the need for reflection on the method with the specified name * and parameter types. @@ -205,6 +236,29 @@ public final class TypeHint implements ConditionalHint { return this; } + /** + * Register the need for reflection on the method with the specified name + * and parameter types, using the specified {@link ExecutableMode}. + * @param name the name of the method + * @param parameterTypes the parameter types of the constructor + * @param mode the requested mode + * @return {@code this}, to facilitate method chaining + */ + public Builder withMethod(String name, List parameterTypes, ExecutableMode mode) { + return withMethod(name, parameterTypes, methodHint -> methodHint.withMode(mode)); + } + + /** + * Register the need for reflection on the method with the specified name + * and parameter types, enabling {@link ExecutableMode#INVOKE}. + * @param name the name of the method + * @param parameterTypes the parameter types of the constructor + * @return {@code this}, to facilitate method chaining + */ + public Builder withMethod(String name, List parameterTypes) { + return withMethod(name, parameterTypes, ExecutableMode.INVOKE); + } + /** * Adds the specified {@linkplain MemberCategory member categories}. * @param memberCategories the categories to apply diff --git a/spring-core/src/test/java/org/springframework/aot/hint/TypeHintTests.java b/spring-core/src/test/java/org/springframework/aot/hint/TypeHintTests.java index f66b83120c4..2bdf754c883 100644 --- a/spring-core/src/test/java/org/springframework/aot/hint/TypeHintTests.java +++ b/spring-core/src/test/java/org/springframework/aot/hint/TypeHintTests.java @@ -17,6 +17,7 @@ package org.springframework.aot.hint; import java.util.List; +import java.util.function.Consumer; import org.junit.jupiter.api.Test; @@ -54,16 +55,38 @@ class TypeHintTests { } @Test - void createWithField() { - TypeHint hint = TypeHint.of(TypeReference.of(String.class)) - .withField("value", fieldHint -> fieldHint.allowWrite(true)).build(); - assertThat(hint.fields()).singleElement().satisfies(fieldHint -> { + void createWithFieldAllowsWriteByDefault() { + assertFieldHint(TypeHint.of(TypeReference.of(String.class)) + .withField("value"), fieldHint -> { assertThat(fieldHint.getName()).isEqualTo("value"); assertThat(fieldHint.isAllowWrite()).isTrue(); assertThat(fieldHint.isAllowUnsafeAccess()).isFalse(); }); } + @Test + void createWithFieldAndEmptyCustomizerAppliesConsistentDefault() { + assertFieldHint(TypeHint.of(TypeReference.of(String.class)) + .withField("value", fieldHint -> {}), fieldHint -> { + assertThat(fieldHint.getName()).isEqualTo("value"); + assertThat(fieldHint.isAllowWrite()).isTrue(); + assertThat(fieldHint.isAllowUnsafeAccess()).isFalse(); + }); + } + + @Test + void createWithFieldAndCustomizerAppliesCustomization() { + assertFieldHint(TypeHint.of(TypeReference.of(String.class)) + .withField("value", fieldHint -> { + fieldHint.allowWrite(false); + fieldHint.allowUnsafeAccess(true); + }), fieldHint -> { + assertThat(fieldHint.getName()).isEqualTo("value"); + assertThat(fieldHint.isAllowWrite()).isFalse(); + assertThat(fieldHint.isAllowUnsafeAccess()).isTrue(); + }); + } + @Test void createWithFieldReuseBuilder() { Builder builder = TypeHint.of(TypeReference.of(String.class)); @@ -72,33 +95,69 @@ class TypeHintTests { fieldHint.allowWrite(true); fieldHint.allowUnsafeAccess(false); }); - TypeHint hint = builder.build(); - assertThat(hint.fields()).singleElement().satisfies(fieldHint -> { + assertFieldHint(builder, fieldHint -> { assertThat(fieldHint.getName()).isEqualTo("value"); assertThat(fieldHint.isAllowWrite()).isTrue(); assertThat(fieldHint.isAllowUnsafeAccess()).isFalse(); }); } + void assertFieldHint(Builder builder, Consumer fieldHint) { + TypeHint hint = builder.build(); + assertThat(hint.fields()).singleElement().satisfies(fieldHint); + assertThat(hint.constructors()).isEmpty(); + assertThat(hint.methods()).isEmpty(); + assertThat(hint.getMemberCategories()).isEmpty(); + } + @Test void createWithConstructor() { List parameterTypes = TypeReference.listOf(byte[].class, int.class); - TypeHint hint = TypeHint.of(TypeReference.of(String.class)).withConstructor(parameterTypes, - constructorHint -> constructorHint.withMode(ExecutableMode.INVOKE)).build(); - assertThat(hint.constructors()).singleElement().satisfies(constructorHint -> { + assertConstructorHint(TypeHint.of(TypeReference.of(String.class)) + .withConstructor(parameterTypes), constructorHint -> { assertThat(constructorHint.getParameterTypes()).containsOnlyOnceElementsOf(parameterTypes); assertThat(constructorHint.getMode()).isEqualTo(ExecutableMode.INVOKE); }); } + @Test + void createWithConstructorAndMode() { + List parameterTypes = TypeReference.listOf(byte[].class, int.class); + assertConstructorHint(TypeHint.of(TypeReference.of(String.class)) + .withConstructor(parameterTypes, ExecutableMode.INTROSPECT), constructorHint -> { + assertThat(constructorHint.getParameterTypes()).containsOnlyOnceElementsOf(parameterTypes); + assertThat(constructorHint.getMode()).isEqualTo(ExecutableMode.INTROSPECT); + }); + } + + @Test + void createWithConstructorAndEmptyCustomizerAppliesConsistentDefault() { + List parameterTypes = TypeReference.listOf(byte[].class, int.class); + assertConstructorHint(TypeHint.of(TypeReference.of(String.class)) + .withConstructor(parameterTypes, constructorHint -> {}), constructorHint -> { + assertThat(constructorHint.getParameterTypes()).containsOnlyOnceElementsOf(parameterTypes); + assertThat(constructorHint.getMode()).isEqualTo(ExecutableMode.INVOKE); + }); + } + + @Test + void createWithConstructorAndCustomizerAppliesCustomization() { + List parameterTypes = TypeReference.listOf(byte[].class, int.class); + assertConstructorHint(TypeHint.of(TypeReference.of(String.class)) + .withConstructor(parameterTypes, constructorHint -> + constructorHint.withMode(ExecutableMode.INTROSPECT)), constructorHint -> { + assertThat(constructorHint.getParameterTypes()).containsOnlyOnceElementsOf(parameterTypes); + assertThat(constructorHint.getMode()).isEqualTo(ExecutableMode.INTROSPECT); + }); + } + @Test void createConstructorReuseBuilder() { List parameterTypes = TypeReference.listOf(byte[].class, int.class); - Builder builder = TypeHint.of(TypeReference.of(String.class)).withConstructor(parameterTypes, - constructorHint -> constructorHint.withMode(ExecutableMode.INTROSPECT)); - TypeHint hint = builder.withConstructor(parameterTypes, constructorHint -> - constructorHint.withMode(ExecutableMode.INVOKE)).build(); - assertThat(hint.constructors()).singleElement().satisfies(constructorHint -> { + Builder builder = TypeHint.of(TypeReference.of(String.class)) + .withConstructor(parameterTypes, ExecutableMode.INTROSPECT); + assertConstructorHint(builder.withConstructor(parameterTypes, constructorHint -> + constructorHint.withMode(ExecutableMode.INVOKE)), constructorHint -> { assertThat(constructorHint.getParameterTypes()).containsExactlyElementsOf(parameterTypes); assertThat(constructorHint.getMode()).isEqualTo(ExecutableMode.INVOKE); }); @@ -109,34 +168,74 @@ class TypeHintTests { List parameterTypes = TypeReference.listOf(byte[].class, int.class); Builder builder = TypeHint.of(TypeReference.of(String.class)).withConstructor(parameterTypes, constructorHint -> constructorHint.withMode(ExecutableMode.INVOKE)); - TypeHint hint = builder.withConstructor(parameterTypes, constructorHint -> - constructorHint.withMode(ExecutableMode.INTROSPECT)).build(); - assertThat(hint.constructors()).singleElement().satisfies(constructorHint -> { + assertConstructorHint(builder.withConstructor(parameterTypes, constructorHint -> + constructorHint.withMode(ExecutableMode.INTROSPECT)), constructorHint -> { assertThat(constructorHint.getParameterTypes()).containsExactlyElementsOf(parameterTypes); assertThat(constructorHint.getMode()).isEqualTo(ExecutableMode.INVOKE); }); } + void assertConstructorHint(Builder builder, Consumer constructorHint) { + TypeHint hint = builder.build(); + assertThat(hint.fields()).isEmpty(); + assertThat(hint.constructors()).singleElement().satisfies(constructorHint); + assertThat(hint.methods()).isEmpty(); + assertThat(hint.getMemberCategories()).isEmpty(); + } + @Test void createWithMethod() { List parameterTypes = List.of(TypeReference.of(char[].class)); - TypeHint hint = TypeHint.of(TypeReference.of(String.class)).withMethod("valueOf", parameterTypes, - methodHint -> methodHint.withMode(ExecutableMode.INVOKE)).build(); - assertThat(hint.methods()).singleElement().satisfies(methodHint -> { + assertMethodHint(TypeHint.of(TypeReference.of(String.class)) + .withMethod("valueOf", parameterTypes), methodHint -> { assertThat(methodHint.getName()).isEqualTo("valueOf"); assertThat(methodHint.getParameterTypes()).containsExactlyElementsOf(parameterTypes); assertThat(methodHint.getMode()).isEqualTo(ExecutableMode.INVOKE); }); } + @Test + void createWithMethodAndMode() { + List parameterTypes = List.of(TypeReference.of(char[].class)); + assertMethodHint(TypeHint.of(TypeReference.of(String.class)) + .withMethod("valueOf", parameterTypes, ExecutableMode.INTROSPECT), methodHint -> { + assertThat(methodHint.getName()).isEqualTo("valueOf"); + assertThat(methodHint.getParameterTypes()).containsExactlyElementsOf(parameterTypes); + assertThat(methodHint.getMode()).isEqualTo(ExecutableMode.INTROSPECT); + }); + } + + @Test + void createWithMethodAndEmptyCustomizerAppliesConsistentDefault() { + List parameterTypes = List.of(TypeReference.of(char[].class)); + assertMethodHint(TypeHint.of(TypeReference.of(String.class)) + .withMethod("valueOf", parameterTypes, methodHint -> {}), methodHint -> { + assertThat(methodHint.getName()).isEqualTo("valueOf"); + assertThat(methodHint.getParameterTypes()).containsExactlyElementsOf(parameterTypes); + assertThat(methodHint.getMode()).isEqualTo(ExecutableMode.INVOKE); + }); + } + + @Test + void createWithMethodAndCustomizerAppliesCustomization() { + List parameterTypes = List.of(TypeReference.of(char[].class)); + assertMethodHint(TypeHint.of(TypeReference.of(String.class)) + .withMethod("valueOf", parameterTypes, methodHint -> + methodHint.withMode(ExecutableMode.INTROSPECT)), methodHint -> { + assertThat(methodHint.getName()).isEqualTo("valueOf"); + assertThat(methodHint.getParameterTypes()).containsExactlyElementsOf(parameterTypes); + assertThat(methodHint.getMode()).isEqualTo(ExecutableMode.INTROSPECT); + }); + } + + @Test void createWithMethodReuseBuilder() { List parameterTypes = TypeReference.listOf(char[].class); - Builder builder = TypeHint.of(TypeReference.of(String.class)).withMethod("valueOf", parameterTypes, - methodHint -> methodHint.withMode(ExecutableMode.INTROSPECT)); - TypeHint hint = builder.withMethod("valueOf", parameterTypes, - methodHint -> methodHint.withMode(ExecutableMode.INVOKE)).build(); - assertThat(hint.methods()).singleElement().satisfies(methodHint -> { + Builder builder = TypeHint.of(TypeReference.of(String.class)) + .withMethod("valueOf", parameterTypes, ExecutableMode.INTROSPECT); + assertMethodHint(builder.withMethod("valueOf", parameterTypes, + methodHint -> methodHint.withMode(ExecutableMode.INVOKE)), methodHint -> { assertThat(methodHint.getName()).isEqualTo("valueOf"); assertThat(methodHint.getParameterTypes()).containsExactlyElementsOf(parameterTypes); assertThat(methodHint.getMode()).isEqualTo(ExecutableMode.INVOKE); @@ -146,17 +245,25 @@ class TypeHintTests { @Test void createWithMethodReuseBuilderAndApplyExecutableModePrecedence() { List parameterTypes = TypeReference.listOf(char[].class); - Builder builder = TypeHint.of(TypeReference.of(String.class)).withMethod("valueOf", parameterTypes, - methodHint -> methodHint.withMode(ExecutableMode.INVOKE)); - TypeHint hint = builder.withMethod("valueOf", parameterTypes, - methodHint -> methodHint.withMode(ExecutableMode.INTROSPECT)).build(); - assertThat(hint.methods()).singleElement().satisfies(methodHint -> { + Builder builder = TypeHint.of(TypeReference.of(String.class)) + .withMethod("valueOf", parameterTypes, ExecutableMode.INVOKE); + assertMethodHint(builder.withMethod("valueOf", parameterTypes, + methodHint -> methodHint.withMode(ExecutableMode.INTROSPECT)), methodHint -> { assertThat(methodHint.getName()).isEqualTo("valueOf"); assertThat(methodHint.getParameterTypes()).containsExactlyElementsOf(parameterTypes); assertThat(methodHint.getMode()).isEqualTo(ExecutableMode.INVOKE); }); } + void assertMethodHint(Builder builder, Consumer methodHint) { + TypeHint hint = builder.build(); + assertThat(hint.fields()).isEmpty(); + assertThat(hint.constructors()).isEmpty(); + assertThat(hint.methods()).singleElement().satisfies(methodHint); + assertThat(hint.getMemberCategories()).isEmpty(); + } + + @Test void createWithMemberCategory() { TypeHint hint = TypeHint.of(TypeReference.of(String.class)) diff --git a/spring-core/src/test/java/org/springframework/aot/hint/predicate/ReflectionHintsPredicatesTests.java b/spring-core/src/test/java/org/springframework/aot/hint/predicate/ReflectionHintsPredicatesTests.java index 3706197d509..6df28fe5711 100644 --- a/spring-core/src/test/java/org/springframework/aot/hint/predicate/ReflectionHintsPredicatesTests.java +++ b/spring-core/src/test/java/org/springframework/aot/hint/predicate/ReflectionHintsPredicatesTests.java @@ -157,7 +157,7 @@ class ReflectionHintsPredicatesTests { @Test void constructorIntrospectionMatchesConstructorHint() { runtimeHints.reflection().registerType(SampleClass.class, typeHint -> - typeHint.withConstructor(Collections.emptyList(), constructorHint -> {})); + typeHint.withConstructor(Collections.emptyList(), ExecutableMode.INTROSPECT)); assertPredicateMatches(reflection.onConstructor(publicConstructor).introspect()); } @@ -192,16 +192,14 @@ class ReflectionHintsPredicatesTests { @Test void constructorInvocationDoesNotMatchConstructorHint() { runtimeHints.reflection().registerType(SampleClass.class, typeHint -> typeHint. - withConstructor(Collections.emptyList(), constructorHint -> - constructorHint.withMode(ExecutableMode.INTROSPECT))); + withConstructor(Collections.emptyList(), ExecutableMode.INTROSPECT)); assertPredicateDoesNotMatch(reflection.onConstructor(publicConstructor).invoke()); } @Test void constructorInvocationMatchesConstructorInvocationHint() { runtimeHints.reflection().registerType(SampleClass.class, typeHint -> typeHint. - withConstructor(Collections.emptyList(), constructorHint -> - constructorHint.withMode(ExecutableMode.INVOKE))); + withConstructor(Collections.emptyList(), ExecutableMode.INVOKE)); assertPredicateMatches(reflection.onConstructor(publicConstructor).invoke()); } @@ -236,7 +234,7 @@ class ReflectionHintsPredicatesTests { @Test void privateConstructorIntrospectionMatchesConstructorHint() { runtimeHints.reflection().registerType(SampleClass.class, typeHint -> - typeHint.withConstructor(TypeReference.listOf(String.class), constructorHint -> {})); + typeHint.withConstructor(TypeReference.listOf(String.class), ExecutableMode.INTROSPECT)); assertPredicateMatches(reflection.onConstructor(privateConstructor).introspect()); } @@ -271,16 +269,14 @@ class ReflectionHintsPredicatesTests { @Test void privateConstructorInvocationDoesNotMatchConstructorHint() { runtimeHints.reflection().registerType(SampleClass.class, typeHint -> - typeHint.withConstructor(TypeReference.listOf(String.class), constructorHint -> - constructorHint.withMode(ExecutableMode.INTROSPECT))); + typeHint.withConstructor(TypeReference.listOf(String.class), ExecutableMode.INTROSPECT)); assertPredicateDoesNotMatch(reflection.onConstructor(privateConstructor).invoke()); } @Test void privateConstructorInvocationMatchesConstructorInvocationHint() { runtimeHints.reflection().registerType(SampleClass.class, typeHint -> - typeHint.withConstructor(TypeReference.listOf(String.class), - constructorHint -> constructorHint.withMode(ExecutableMode.INVOKE))); + typeHint.withConstructor(TypeReference.listOf(String.class), ExecutableMode.INVOKE)); assertPredicateMatches(reflection.onConstructor(privateConstructor).invoke()); } @@ -319,8 +315,8 @@ class ReflectionHintsPredicatesTests { @Test void methodIntrospectionMatchesMethodHint() { - runtimeHints.reflection().registerType(SampleClass.class, typeHint -> typeHint.withMethod("publicMethod", Collections.emptyList(), methodHint -> { - })); + runtimeHints.reflection().registerType(SampleClass.class, typeHint -> + typeHint.withMethod("publicMethod", Collections.emptyList(), ExecutableMode.INTROSPECT)); assertPredicateMatches(reflection.onMethod(SampleClass.class, "publicMethod").introspect()); } @@ -350,15 +346,15 @@ class ReflectionHintsPredicatesTests { @Test void methodInvocationDoesNotMatchMethodHint() { - runtimeHints.reflection().registerType(SampleClass.class, typeHint -> typeHint.withMethod("publicMethod", Collections.emptyList(), - methodHint -> methodHint.withMode(ExecutableMode.INTROSPECT))); + runtimeHints.reflection().registerType(SampleClass.class, typeHint -> + typeHint.withMethod("publicMethod", Collections.emptyList(), ExecutableMode.INTROSPECT)); assertPredicateDoesNotMatch(reflection.onMethod(SampleClass.class, "publicMethod").invoke()); } @Test void methodInvocationMatchesMethodInvocationHint() { - runtimeHints.reflection().registerType(SampleClass.class, typeHint -> typeHint.withMethod("publicMethod", Collections.emptyList(), - methodHint -> methodHint.withMode(ExecutableMode.INVOKE))); + runtimeHints.reflection().registerType(SampleClass.class, typeHint -> + typeHint.withMethod("publicMethod", Collections.emptyList(), ExecutableMode.INVOKE)); assertPredicateMatches(reflection.onMethod(SampleClass.class, "publicMethod").invoke()); } @@ -388,8 +384,8 @@ class ReflectionHintsPredicatesTests { @Test void privateMethodIntrospectionMatchesMethodHint() { - runtimeHints.reflection().registerType(SampleClass.class, typeHint -> typeHint.withMethod("privateMethod", Collections.emptyList(), methodHint -> { - })); + runtimeHints.reflection().registerType(SampleClass.class, typeHint -> + typeHint.withMethod("privateMethod", Collections.emptyList(), ExecutableMode.INTROSPECT)); assertPredicateMatches(reflection.onMethod(SampleClass.class, "privateMethod").introspect()); } @@ -419,14 +415,15 @@ class ReflectionHintsPredicatesTests { @Test void privateMethodInvocationDoesNotMatchMethodHint() { - runtimeHints.reflection().registerType(SampleClass.class, typeHint -> typeHint.withMethod("privateMethod", Collections.emptyList(), - methodHint -> methodHint.withMode(ExecutableMode.INTROSPECT))); + runtimeHints.reflection().registerType(SampleClass.class, typeHint -> + typeHint.withMethod("privateMethod", Collections.emptyList(), ExecutableMode.INTROSPECT)); assertPredicateDoesNotMatch(reflection.onMethod(SampleClass.class, "privateMethod").invoke()); } @Test void privateMethodInvocationMatchesMethodInvocationHint() { - runtimeHints.reflection().registerType(SampleClass.class, typeHint -> typeHint.withMethod("privateMethod", Collections.emptyList(), methodHint -> methodHint.withMode(ExecutableMode.INVOKE))); + runtimeHints.reflection().registerType(SampleClass.class, typeHint -> + typeHint.withMethod("privateMethod", Collections.emptyList(), ExecutableMode.INVOKE)); assertPredicateMatches(reflection.onMethod(SampleClass.class, "privateMethod").invoke()); } diff --git a/spring-core/src/test/java/org/springframework/aot/nativex/FileNativeConfigurationWriterTests.java b/spring-core/src/test/java/org/springframework/aot/nativex/FileNativeConfigurationWriterTests.java index 121e75c9ebf..833ad61d6f9 100644 --- a/spring-core/src/test/java/org/springframework/aot/nativex/FileNativeConfigurationWriterTests.java +++ b/spring-core/src/test/java/org/springframework/aot/nativex/FileNativeConfigurationWriterTests.java @@ -111,11 +111,9 @@ public class FileNativeConfigurationWriterTests { fieldBuilder.allowWrite(true); fieldBuilder.allowUnsafeAccess(true); }) - .withConstructor(TypeReference.listOf(List.class, boolean.class, MimeType.class), constructorHint -> - constructorHint.withMode(ExecutableMode.INTROSPECT)) - .withMethod("setDefaultCharset", TypeReference.listOf(Charset.class), ctorBuilder -> {}) - .withMethod("getDefaultCharset", Collections.emptyList(), constructorHint -> - constructorHint.withMode(ExecutableMode.INTROSPECT)); + .withConstructor(TypeReference.listOf(List.class, boolean.class, MimeType.class), ExecutableMode.INTROSPECT) + .withMethod("setDefaultCharset", TypeReference.listOf(Charset.class)) + .withMethod("getDefaultCharset", Collections.emptyList(), ExecutableMode.INTROSPECT); }); generator.write(hints); assertEquals(""" diff --git a/spring-core/src/test/java/org/springframework/aot/nativex/ReflectionHintsWriterTests.java b/spring-core/src/test/java/org/springframework/aot/nativex/ReflectionHintsWriterTests.java index e3c547a79b2..62abe02908c 100644 --- a/spring-core/src/test/java/org/springframework/aot/nativex/ReflectionHintsWriterTests.java +++ b/spring-core/src/test/java/org/springframework/aot/nativex/ReflectionHintsWriterTests.java @@ -49,26 +49,22 @@ public class ReflectionHintsWriterTests { @Test void one() throws JSONException { ReflectionHints hints = new ReflectionHints(); - hints.registerType(StringDecoder.class, builder -> { - builder - .onReachableType(TypeReference.of(String.class)) - .withMembers(MemberCategory.PUBLIC_FIELDS, MemberCategory.DECLARED_FIELDS, - MemberCategory.INTROSPECT_PUBLIC_CONSTRUCTORS, MemberCategory.INTROSPECT_DECLARED_CONSTRUCTORS, - MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, - MemberCategory.INTROSPECT_PUBLIC_METHODS, MemberCategory.INTROSPECT_DECLARED_METHODS, - MemberCategory.INVOKE_PUBLIC_METHODS, MemberCategory.INVOKE_DECLARED_METHODS, - MemberCategory.PUBLIC_CLASSES, MemberCategory.DECLARED_CLASSES) - .withField("DEFAULT_CHARSET", fieldBuilder -> fieldBuilder.allowWrite(false)) - .withField("defaultCharset", fieldBuilder -> { - fieldBuilder.allowWrite(true); - fieldBuilder.allowUnsafeAccess(true); - }) - .withConstructor(TypeReference.listOf(List.class, boolean.class, MimeType.class), constructorHint -> - constructorHint.withMode(ExecutableMode.INTROSPECT)) - .withMethod("setDefaultCharset", List.of(TypeReference.of(Charset.class)), ctorBuilder -> {}) - .withMethod("getDefaultCharset", Collections.emptyList(), constructorHint -> - constructorHint.withMode(ExecutableMode.INTROSPECT)); - }); + hints.registerType(StringDecoder.class, builder -> builder + .onReachableType(TypeReference.of(String.class)) + .withMembers(MemberCategory.PUBLIC_FIELDS, MemberCategory.DECLARED_FIELDS, + MemberCategory.INTROSPECT_PUBLIC_CONSTRUCTORS, MemberCategory.INTROSPECT_DECLARED_CONSTRUCTORS, + MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, + MemberCategory.INTROSPECT_PUBLIC_METHODS, MemberCategory.INTROSPECT_DECLARED_METHODS, + MemberCategory.INVOKE_PUBLIC_METHODS, MemberCategory.INVOKE_DECLARED_METHODS, + MemberCategory.PUBLIC_CLASSES, MemberCategory.DECLARED_CLASSES) + .withField("DEFAULT_CHARSET", fieldBuilder -> fieldBuilder.allowWrite(false)) + .withField("defaultCharset", fieldBuilder -> { + fieldBuilder.allowWrite(true); + fieldBuilder.allowUnsafeAccess(true); + }) + .withConstructor(TypeReference.listOf(List.class, boolean.class, MimeType.class), ExecutableMode.INTROSPECT) + .withMethod("setDefaultCharset", List.of(TypeReference.of(Charset.class))) + .withMethod("getDefaultCharset", Collections.emptyList(), ExecutableMode.INTROSPECT)); assertEquals(""" [ { @@ -120,7 +116,7 @@ public class ReflectionHintsWriterTests { void queriedMethods() throws JSONException { ReflectionHints hints = new ReflectionHints(); hints.registerType(Integer.class, builder -> builder.withMethod("parseInt", - TypeReference.listOf(String.class), b -> b.withMode(ExecutableMode.INTROSPECT))); + TypeReference.listOf(String.class), ExecutableMode.INTROSPECT)); assertEquals(""" [ @@ -141,7 +137,7 @@ public class ReflectionHintsWriterTests { void methods() throws JSONException { ReflectionHints hints = new ReflectionHints(); hints.registerType(Integer.class, builder -> builder.withMethod("parseInt", - TypeReference.listOf(String.class), b -> b.withMode(ExecutableMode.INVOKE))); + TypeReference.listOf(String.class), ExecutableMode.INVOKE)); assertEquals(""" [ @@ -162,7 +158,7 @@ public class ReflectionHintsWriterTests { void methodWithInnerClassParameter() throws JSONException { ReflectionHints hints = new ReflectionHints(); hints.registerType(Integer.class, builder -> builder.withMethod("test", - TypeReference.listOf(Inner.class), b -> b.withMode(ExecutableMode.INVOKE))); + TypeReference.listOf(Inner.class), ExecutableMode.INVOKE)); assertEquals(""" [ @@ -183,9 +179,9 @@ public class ReflectionHintsWriterTests { void methodAndQueriedMethods() throws JSONException { ReflectionHints hints = new ReflectionHints(); hints.registerType(Integer.class, builder -> builder.withMethod("parseInt", - TypeReference.listOf(String.class), b -> b.withMode(ExecutableMode.INVOKE))); + TypeReference.listOf(String.class), ExecutableMode.INVOKE)); hints.registerType(Integer.class, builder -> builder.withMethod("parseInt", - TypeReference.listOf(String.class, int.class), b -> b.withMode(ExecutableMode.INTROSPECT))); + TypeReference.listOf(String.class, int.class), ExecutableMode.INTROSPECT)); assertEquals(""" [