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 8ce01cb68c0..8e85a04be77 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 @@ -20,6 +20,7 @@ import java.lang.reflect.Constructor; import java.lang.reflect.Executable; import java.lang.reflect.Method; import java.util.List; +import java.util.function.Consumer; import org.springframework.lang.Nullable; import org.springframework.util.Assert; @@ -80,6 +81,15 @@ public final class ExecutableHint extends MemberHint { return this.mode; } + /** + * Return a {@link Consumer} that applies the given {@link ExecutableMode} + * to the accepted {@link Builder}. + * @param mode the mode to apply + * @return a consumer to apply the mode + */ + public static Consumer builtWith(ExecutableMode mode) { + return builder -> builder.withMode(mode); + } /** * Builder for {@link ExecutableHint}. 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 9516b47cfac..f2604a75fb5 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 @@ -17,6 +17,7 @@ package org.springframework.aot.hint; import java.lang.reflect.Field; +import java.util.function.Consumer; import org.springframework.lang.Nullable; import org.springframework.util.Assert; @@ -66,6 +67,16 @@ public final class FieldHint extends MemberHint { return this.allowUnsafeAccess; } + /** + * Return a {@link Consumer} that applies the given {@link FieldMode} + * to the accepted {@link Builder}. + * @param mode the mode to apply + * @return a consumer to apply the mode + */ + public static Consumer builtWith(FieldMode mode) { + return builder -> builder.withMode(mode); + } + /** * Builder for {@link FieldHint}. 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 eb1beb2ec82..a656a74f350 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 @@ -86,6 +86,28 @@ public class ReflectionHints { return this; } + /** + * Register or customize reflection hints for the specified type + * using the specified {@link MemberCategory MemberCategories}. + * @param type the type to customize + * @param memberCategories the member categories to apply + * @return {@code this}, to facilitate method chaining + */ + public ReflectionHints registerType(Class type, MemberCategory... memberCategories) { + return registerType(TypeReference.of(type), memberCategories); + } + + /** + * Register or customize reflection hints for the specified type + * using the specified {@link MemberCategory MemberCategories}. + * @param type the type to customize + * @param memberCategories the member categories to apply + * @return {@code this}, to facilitate method chaining + */ + public ReflectionHints registerType(TypeReference type , MemberCategory... memberCategories) { + return registerType(type, TypeHint.builtWith(memberCategories)); + } + /** * Register or customize reflection hints for the specified type. * @param type the type to customize @@ -132,7 +154,17 @@ public class ReflectionHints { * @return {@code this}, to facilitate method chaining */ public ReflectionHints registerField(Field field) { - return registerField(field, fieldHint -> fieldHint.withMode(FieldMode.WRITE)); + return registerField(field, FieldMode.WRITE); + } + + /** + * Register the need for reflection on the specified {@link Field} + * using the specified {@link FieldMode}. + * @param field the field that requires reflection + * @return {@code this}, to facilitate method chaining + */ + public ReflectionHints registerField(Field field, FieldMode mode) { + return registerField(field, FieldHint.builtWith(mode)); } /** @@ -164,7 +196,7 @@ public class ReflectionHints { * @return {@code this}, to facilitate method chaining */ public ReflectionHints registerConstructor(Constructor constructor, ExecutableMode mode) { - return registerConstructor(constructor, constructorHint -> constructorHint.withMode(mode)); + return registerConstructor(constructor, ExecutableHint.builtWith(mode)); } /** @@ -197,7 +229,7 @@ public class ReflectionHints { * @return {@code this}, to facilitate method chaining */ public ReflectionHints registerMethod(Method method, ExecutableMode mode) { - return registerMethod(method, methodHint -> methodHint.withMode(mode)); + return registerMethod(method, ExecutableHint.builtWith(mode)); } /** 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 5e55c712381..95432587add 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 @@ -126,6 +126,17 @@ public final class TypeHint implements ConditionalHint { .toString(); } + /** + * Return a {@link Consumer} that applies the given {@link MemberCategory + * MemberCategories} to the accepted {@link Builder}. + * @param memberCategories the memberCategories to apply + * @return a consumer to apply the member categories + */ + public static Consumer builtWith(MemberCategory... memberCategories) { + return builder -> builder.withMembers(memberCategories); + } + + /** * Builder for {@link TypeHint}. */ @@ -180,7 +191,18 @@ public final class TypeHint implements ConditionalHint { * @return {@code this}, to facilitate method chaining */ public Builder withField(String name) { - return withField(name, fieldHint -> {}); + return withField(name, FieldMode.WRITE); + } + + /** + * Register the need for reflection on the field with the specified name + * using the specified {@link FieldMode}. + * @param name the name of the field + * @param mode the requested mode + * @return {@code this}, to facilitate method chaining + */ + public Builder withField(String name, FieldMode mode) { + return withField(name, FieldHint.builtWith(mode)); } /** @@ -213,7 +235,7 @@ public final class TypeHint implements ConditionalHint { * @return {@code this}, to facilitate method chaining */ public Builder withConstructor(List parameterTypes, ExecutableMode mode) { - return withConstructor(parameterTypes, constructorHint -> constructorHint.withMode(mode)); + return withConstructor(parameterTypes, ExecutableHint.builtWith(mode)); } /** @@ -252,7 +274,7 @@ public final class TypeHint implements ConditionalHint { * @return {@code this}, to facilitate method chaining */ public Builder withMethod(String name, List parameterTypes, ExecutableMode mode) { - return withMethod(name, parameterTypes, methodHint -> methodHint.withMode(mode)); + return withMethod(name, parameterTypes, ExecutableHint.builtWith(mode)); } /** diff --git a/spring-core/src/test/java/org/springframework/aot/hint/ExecutableHintTests.java b/spring-core/src/test/java/org/springframework/aot/hint/ExecutableHintTests.java new file mode 100644 index 00000000000..f817ba56b82 --- /dev/null +++ b/spring-core/src/test/java/org/springframework/aot/hint/ExecutableHintTests.java @@ -0,0 +1,41 @@ +/* + * 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 java.util.Collections; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link ExecutableHint}. + * + * @author Phillip Webb + * @since 6.0 + */ +class ExecutableHintTests { + + @Test + void builtWithAppliesMode() { + ExecutableHint.Builder builder = new ExecutableHint.Builder("test", Collections.emptyList()); + assertThat(builder.build().getMode()).isEqualTo(ExecutableMode.INVOKE); + ExecutableHint.builtWith(ExecutableMode.INTROSPECT).accept(builder); + assertThat(builder.build().getMode()).isEqualTo(ExecutableMode.INTROSPECT); + } + +} diff --git a/spring-core/src/test/java/org/springframework/aot/hint/FieldHintTests.java b/spring-core/src/test/java/org/springframework/aot/hint/FieldHintTests.java new file mode 100644 index 00000000000..8c89a21e567 --- /dev/null +++ b/spring-core/src/test/java/org/springframework/aot/hint/FieldHintTests.java @@ -0,0 +1,38 @@ +/* + * 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 FieldHint}. + * + * @author Phillip Webb + */ +class FieldHintTests { + + @Test + void builtWithAppliesMode() { + FieldHint.Builder builder = new FieldHint.Builder("test"); + assertThat(builder.build().getMode()).isEqualTo(FieldMode.WRITE); + FieldHint.builtWith(FieldMode.READ).accept(builder); + assertThat(builder.build().getMode()).isEqualTo(FieldMode.READ); + } + +} 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 1aafef6ea15..14bd8fc4d48 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 @@ -106,6 +106,14 @@ class ReflectionHintsTests { typeWithMemberCategories(Integer.class, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS)); } + @Test + void registerClassWitCustomizer() { + this.reflectionHints.registerType(Integer.class, + typeHint -> typeHint.withMembers(MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS)); + assertThat(this.reflectionHints.typeHints()).singleElement().satisfies( + typeWithMemberCategories(Integer.class, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS)); + } + @Test void registerTypesApplyTheSameHints() { this.reflectionHints.registerTypes(TypeReference.listOf(Integer.class, String.class, Double.class), @@ -157,6 +165,17 @@ class ReflectionHintsTests { }); } + @Test + void registerFieldWithMode() { + Field field = ReflectionUtils.findField(TestType.class, "field"); + assertThat(field).isNotNull(); + this.reflectionHints.registerField(field, FieldMode.READ); + assertTestTypeFieldHint(fieldHint -> { + assertThat(fieldHint.getName()).isEqualTo("field"); + assertThat(fieldHint.getMode()).isEqualTo(FieldMode.READ); + }); + } + @Test // gh-29055 void registerFieldWithCustomizersCannotDowngradeWrite() { Field field = ReflectionUtils.findField(TestType.class, "field"); 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 e5490e026de..aef5bebf9bc 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 @@ -102,6 +102,17 @@ class TypeHintTests { }); } + @Test + void createFieldWithFieldMode() { + Builder builder = TypeHint.of(TypeReference.of(String.class)); + builder.withField("value", FieldMode.READ); + assertFieldHint(builder, fieldHint -> { + assertThat(fieldHint.getName()).isEqualTo("value"); + assertThat(fieldHint.getMode()).isEqualTo(FieldMode.READ); + assertThat(fieldHint.isAllowUnsafeAccess()).isFalse(); + }); + } + void assertFieldHint(Builder builder, Consumer fieldHint) { TypeHint hint = builder.build(); assertThat(hint.fields()).singleElement().satisfies(fieldHint); @@ -277,4 +288,13 @@ class TypeHintTests { assertThat(hint).hasToString("TypeHint[type=java.lang.String]"); } + @Test + void builtWithAppliesMemberCategories() { + TypeHint.Builder builder = new TypeHint.Builder(TypeReference.of(String.class)); + assertThat(builder.build().getMemberCategories()).isEmpty(); + TypeHint.builtWith(MemberCategory.DECLARED_CLASSES, MemberCategory.DECLARED_FIELDS).accept(builder); + assertThat(builder.build().getMemberCategories()).containsExactlyInAnyOrder(MemberCategory.DECLARED_CLASSES, + MemberCategory.DECLARED_FIELDS); + } + }