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 e00d3a7792..78378f6b0a 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 76330a04f5..d3d4b4de38 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 be7bc11d58..c59c8fc01d 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 483638bf67..b6f09e3d11 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 db06fafd89..cd11fdc1b0 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 9d5599ad83..7082859e75 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 d6976f2034..593d5b5588 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 c5386cfdbb..b9460ed01b 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 4ad5e9363d..6d4d87277a 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 cd4c902a84..61b7dbd845 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 bd22234210..d370570c9a 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 0000000000..d19df791de --- /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 68458881dd..24799c99da 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 e781352c52..f66b83120c 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 4ab2c603d8..45bf6f7c9d 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 d3390fe15b..3706197d50 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 dc77407689..121e75c9eb 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 9060f5b0af..e3c547a79b 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 f1004dc6ba..4f7ac3942f 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 c1661d39f6..d61868aeb0 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 9df071e809..a1cff64cbb 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 a85646d1f9..39380e784d 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 1728e41ee3..66bba16204 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 ad0af7827e..c75612dbec 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 b9460ed01b..a30046c2cf 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 24799c99da..8ab8d7180d 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 4f7ac3942f..8fa0be9f60 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 d61868aeb0..ac45533aee 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 b6f09e3d11..d5a063e3b8 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 519eba94a6..e7781caabd 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 f66b83120c..2bdf754c88 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 3706197d50..6df28fe571 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 121e75c9eb..833ad61d6f 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 e3c547a79b..62abe02908 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(""" [