Fix hints and predicates for Field reflective access

This commit revisits the arrangement for Field hints after changes made
in gh-34239.

Closes gh-34294
This commit is contained in:
Brian Clozel
2025-01-21 20:05:12 +01:00
parent 3302bc46f8
commit f85752a956
21 changed files with 115 additions and 145 deletions

View File

@@ -100,7 +100,7 @@ public class BindingReflectionHintsRegistrar {
typeHint.withMembers(MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS,
MemberCategory.INVOKE_PUBLIC_METHODS);
}
typeHint.withMembers(MemberCategory.INVOKE_DECLARED_FIELDS,
typeHint.withMembers(MemberCategory.ACCESS_DECLARED_FIELDS,
MemberCategory.INVOKE_DECLARED_CONSTRUCTORS);
for (Method method : clazz.getMethods()) {
String methodName = method.getName();

View File

@@ -33,7 +33,7 @@ public enum MemberCategory {
/**
* A category that represents reflective field access on public {@linkplain Field fields}.
* @deprecated in favor of @link #INVOKE_PUBLIC_FIELDS} with similar semantics.
* @deprecated in favor of {@link #ACCESS_PUBLIC_FIELDS} with similar semantics.
* @see Field#get(Object)
* @see Field#set(Object, Object)
*/
@@ -44,7 +44,7 @@ public enum MemberCategory {
* A category that represents reflective field access on
* {@linkplain Class#getDeclaredFields() declared fields}: all fields defined by the
* class but not inherited fields.
* @deprecated in favor of @link #INVOKE_DECLARED_FIELDS} with similar semantics.
* @deprecated in favor of {@link #ACCESS_DECLARED_FIELDS} with similar semantics.
* @see Class#getDeclaredFields()
* @see Field#get(Object)
* @see Field#set(Object, Object)
@@ -53,20 +53,23 @@ public enum MemberCategory {
DECLARED_FIELDS,
/**
* A category that represents getting/setting values on public {@linkplain Field fields}.
* A category that represents reflective field access on public {@linkplain Field fields}..
* @see Field#get(Object)
* @see Field#set(Object, Object)
* @since 7.0
*/
INVOKE_PUBLIC_FIELDS,
ACCESS_PUBLIC_FIELDS,
/**
* A category that represents getting/setting values on declared {@linkplain Field fields}.
* A category that represents reflective field access on
* {@linkplain Class#getDeclaredFields() declared fields}: all fields defined by the
* class but not inherited fields.
* @see Class#getDeclaredFields()
* @see Field#get(Object)
* @see Field#set(Object, Object)
* @since 7.0
*/
INVOKE_DECLARED_FIELDS,
ACCESS_DECLARED_FIELDS,
/**
* A category that defines public {@linkplain Constructor constructors} can

View File

@@ -212,19 +212,29 @@ public class ReflectionHintsPredicates {
}
/**
* Return a predicate that checks whether a reflection hint is registered for the field that matches the given selector.
* Return a predicate that checks whether a reflective field access hint is registered for the field.
* This looks up a field on the given type with the expected name, if present.
* By default, unsafe or write access is not considered.
* <p>The returned type exposes additional methods that refine the predicate behavior.
* @param type the type holding the field
* @param fieldName the field name
* @return the {@link RuntimeHints} predicate
* @throws IllegalArgumentException if a field cannot be found with the given name.
* @deprecated since 7.0 in favor of {@link #onFieldInvocation(Class, String)}
* or {@link #onType(Class)}.
* @deprecated since 7.0 in favor of {@link #onFieldAccess(Class, String)} with similar semantics.
*/
@Deprecated(since = "7.0", forRemoval = true)
public FieldHintPredicate onField(Class<?> type, String fieldName) {
public Predicate<RuntimeHints> onField(Class<?> type, String fieldName) {
return onFieldAccess(type, fieldName);
}
/**
* Return a predicate that checks whether a reflective field access hint is registered for the field.
* This looks up a field on the given type with the expected name, if present.
* @param type the type holding the field
* @param fieldName the field name
* @return the {@link RuntimeHints} predicate
* @throws IllegalArgumentException if a field cannot be found with the given name.
* @since 7.0
*/
public Predicate<RuntimeHints> onFieldAccess(Class<?> type, String fieldName) {
Assert.notNull(type, "'type' must not be null");
Assert.hasText(fieldName, "'fieldName' must not be empty");
Field field = ReflectionUtils.findField(type, fieldName);
@@ -235,46 +245,22 @@ public class ReflectionHintsPredicates {
}
/**
* Return a predicate that checks whether an invocation hint is registered for the field that matches the given selector.
* Return a predicate that checks whether a reflective field access hint is registered for the field.
* This looks up a field on the given type with the expected name, if present.
* @param type the type holding the field
* @param fieldName the field name
* @return the {@link RuntimeHints} predicate
* @throws IllegalArgumentException if a field cannot be found with the given name.
* @since 7.0
*/
public Predicate<RuntimeHints> onFieldInvocation(Class<?> type, String fieldName) {
Assert.notNull(type, "'type' must not be null");
Assert.hasText(fieldName, "'fieldName' must not be empty");
Field field = ReflectionUtils.findField(type, fieldName);
if (field == null) {
throw new IllegalArgumentException("No field named '%s' on class %s".formatted(fieldName, type.getName()));
}
return new FieldHintPredicate(field).invocation();
}
/**
* Return a predicate that checks whether a reflection hint is registered for the field that matches the given selector.
* This looks up a field on the given type with the expected name, if present.
* By default, unsafe or write access is not considered.
* <p>The returned type exposes additional methods that refine the predicate behavior.
* @param className the name of the class holding the field
* @param fieldName the field name
* @return the {@link RuntimeHints} predicate
* @throws ClassNotFoundException if the class cannot be resolved.
* @throws IllegalArgumentException if a field cannot be found with the given name.
* @deprecated since 7.0 in favor of {@link #onFieldInvocation(String, String)}
* or {@link #onType(Class)}.
* @deprecated since 7.0 in favor of {@link #onFieldAccess(String, String)} with similar semantics.
*/
@Deprecated(since = "7.0", forRemoval = true)
public FieldHintPredicate onField(String className, String fieldName) throws ClassNotFoundException {
Assert.hasText(className, "'className' must not be empty");
Assert.hasText(fieldName, "'fieldName' must not be empty");
return onField(Class.forName(className), fieldName);
public Predicate<RuntimeHints> onField(String className, String fieldName) throws ClassNotFoundException {
return onFieldAccess(className, fieldName);
}
/**
* Return a predicate that checks whether an invocation hint is registered for the field that matches the given selector.
* Return a predicate that checks whether an invocation hint is registered for the field.
* This looks up a field on the given type with the expected name, if present.
* @param className the name of the class holding the field
* @param fieldName the field name
@@ -283,23 +269,21 @@ public class ReflectionHintsPredicates {
* @throws IllegalArgumentException if a field cannot be found with the given name.
* @since 7.0
*/
public Predicate<RuntimeHints> onFieldInvocation(String className, String fieldName) throws ClassNotFoundException {
public Predicate<RuntimeHints> onFieldAccess(String className, String fieldName) throws ClassNotFoundException {
Assert.hasText(className, "'className' must not be empty");
Assert.hasText(fieldName, "'fieldName' must not be empty");
return onField(Class.forName(className), fieldName).invocation();
return onFieldAccess(Class.forName(className), fieldName);
}
/**
* Return a predicate that checks whether a reflective field access hint is registered for the given field.
* @param field the field
* @return the {@link RuntimeHints} predicate
* @deprecated since 7.0 in favor of {@link #onFieldInvocation(Field)}
* or {@link #onType(Class)}.
* @deprecated since 7.0 in favor of {@link #onFieldAccess(Field)} with similar semantics.
*/
@Deprecated(since = "7.0", forRemoval = true)
public FieldHintPredicate onField(Field field) {
Assert.notNull(field, "'field' must not be null");
return new FieldHintPredicate(field);
public Predicate<RuntimeHints> onField(Field field) {
return onFieldAccess(field);
}
/**
@@ -308,9 +292,9 @@ public class ReflectionHintsPredicates {
* @return the {@link RuntimeHints} predicate
* @since 7.0
*/
public Predicate<RuntimeHints> onFieldInvocation(Field field) {
public Predicate<RuntimeHints> onFieldAccess(Field field) {
Assert.notNull(field, "'field' must not be null");
return new FieldHintPredicate(field).invocation();
return new FieldHintPredicate(field);
}
@@ -494,39 +478,34 @@ public class ReflectionHintsPredicates {
private final Field field;
private @Nullable ExecutableMode executableMode;
FieldHintPredicate(Field field) {
this.field = field;
}
/**
* Refine the current predicate to only match if an invocation hint is registered for this field.
* @return the refined {@link RuntimeHints} predicate
* @since 7.0
*/
public FieldHintPredicate invocation() {
this.executableMode = ExecutableMode.INVOKE;
return this;
}
@Override
public boolean test(RuntimeHints runtimeHints) {
TypeHint typeHint = runtimeHints.reflection().getTypeHint(this.field.getDeclaringClass());
if (typeHint != null) {
if (this.executableMode == ExecutableMode.INVOKE) {
if (Modifier.isPublic(this.field.getModifiers())) {
return typeHint.getMemberCategories().contains(MemberCategory.INVOKE_PUBLIC_FIELDS);
}
else {
return typeHint.getMemberCategories().contains(MemberCategory.INVOKE_DECLARED_FIELDS);
}
}
else {
return true;
}
if (typeHint == null) {
return false;
}
return false;
return memberCategoryMatch(typeHint) || exactMatch(typeHint);
}
@SuppressWarnings("removal")
private boolean memberCategoryMatch(TypeHint typeHint) {
if (Modifier.isPublic(this.field.getModifiers())) {
return typeHint.getMemberCategories().contains(MemberCategory.ACCESS_PUBLIC_FIELDS)
|| typeHint.getMemberCategories().contains(MemberCategory.PUBLIC_FIELDS);
}
else {
return typeHint.getMemberCategories().contains(MemberCategory.ACCESS_DECLARED_FIELDS)
|| typeHint.getMemberCategories().contains(MemberCategory.DECLARED_FIELDS);
}
}
private boolean exactMatch(TypeHint typeHint) {
return typeHint.fields().anyMatch(fieldHint ->
this.field.getName().equals(fieldHint.getName()));
}
}

View File

@@ -44,7 +44,7 @@ public abstract class ClassHintUtils {
private static final Consumer<TypeHint.Builder> asClassBasedProxy = hint ->
hint.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS,
MemberCategory.INVOKE_DECLARED_METHODS,
MemberCategory.INVOKE_DECLARED_FIELDS);
MemberCategory.ACCESS_DECLARED_FIELDS);
private static final Consumer<TypeHint.Builder> asProxiedUserClass = hint ->
hint.withMembers(MemberCategory.INVOKE_PUBLIC_METHODS,

View File

@@ -121,8 +121,8 @@ class ReflectionHintsAttributes {
private void handleCategories(Map<String, Object> attributes, Set<MemberCategory> categories) {
categories.stream().sorted().forEach(category -> {
switch (category) {
case INVOKE_PUBLIC_FIELDS, PUBLIC_FIELDS -> attributes.put("allPublicFields", true);
case INVOKE_DECLARED_FIELDS, DECLARED_FIELDS -> attributes.put("allDeclaredFields", true);
case ACCESS_PUBLIC_FIELDS, PUBLIC_FIELDS -> attributes.put("allPublicFields", true);
case ACCESS_DECLARED_FIELDS, DECLARED_FIELDS -> attributes.put("allDeclaredFields", true);
case INVOKE_PUBLIC_CONSTRUCTORS ->
attributes.put("allPublicConstructors", true);
case INVOKE_DECLARED_CONSTRUCTORS ->

View File

@@ -54,7 +54,7 @@ class BindingReflectionHintsRegistrarTests {
.satisfies(typeHint -> {
assertThat(typeHint.getType()).isEqualTo(TypeReference.of(SampleEmptyClass.class));
assertThat(typeHint.getMemberCategories()).containsExactlyInAnyOrder(
MemberCategory.INVOKE_DECLARED_FIELDS, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS);
MemberCategory.ACCESS_DECLARED_FIELDS, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS);
assertThat(typeHint.constructors()).isEmpty();
assertThat(typeHint.fields()).isEmpty();
assertThat(typeHint.methods()).isEmpty();
@@ -68,7 +68,7 @@ class BindingReflectionHintsRegistrarTests {
typeHint -> {
assertThat(typeHint.getType()).isEqualTo(TypeReference.of(SampleEmptyClass.class));
assertThat(typeHint.getMemberCategories()).containsExactlyInAnyOrder(
MemberCategory.INVOKE_DECLARED_FIELDS, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS);
MemberCategory.ACCESS_DECLARED_FIELDS, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS);
assertThat(typeHint.constructors()).isEmpty();
assertThat(typeHint.fields()).isEmpty();
assertThat(typeHint.methods()).isEmpty();
@@ -76,7 +76,7 @@ class BindingReflectionHintsRegistrarTests {
typeHint -> {
assertThat(typeHint.getType()).isEqualTo(TypeReference.of(SampleExtendingClass.class));
assertThat(typeHint.getMemberCategories()).containsExactlyInAnyOrder(
MemberCategory.INVOKE_DECLARED_FIELDS, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS);
MemberCategory.ACCESS_DECLARED_FIELDS, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS);
assertThat(typeHint.constructors()).isEmpty();
assertThat(typeHint.fields()).isEmpty();
assertThat(typeHint.methods()).isEmpty();
@@ -198,7 +198,7 @@ class BindingReflectionHintsRegistrarTests {
typeHint -> {
assertThat(typeHint.getType()).isEqualTo(TypeReference.of(ResolvableType.class));
assertThat(typeHint.getMemberCategories()).containsExactlyInAnyOrder(
MemberCategory.INVOKE_DECLARED_FIELDS, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS);
MemberCategory.ACCESS_DECLARED_FIELDS, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS);
assertThat(typeHint.constructors()).isEmpty();
assertThat(typeHint.fields()).isEmpty();
assertThat(typeHint.methods()).hasSizeGreaterThan(1);
@@ -267,7 +267,7 @@ class BindingReflectionHintsRegistrarTests {
@Test
void registerTypeForJacksonAnnotations() {
bindingRegistrar.registerReflectionHints(this.hints.reflection(), SampleClassWithJsonProperty.class);
assertThat(RuntimeHintsPredicates.reflection().onFieldInvocation(SampleClassWithJsonProperty.class, "privateField"))
assertThat(RuntimeHintsPredicates.reflection().onFieldAccess(SampleClassWithJsonProperty.class, "privateField"))
.accepts(this.hints);
assertThat(RuntimeHintsPredicates.reflection().onMethodInvocation(SampleClassWithJsonProperty.class, "packagePrivateMethod"))
.accepts(this.hints);
@@ -276,7 +276,7 @@ class BindingReflectionHintsRegistrarTests {
@Test
void registerTypeForInheritedJacksonAnnotations() {
bindingRegistrar.registerReflectionHints(this.hints.reflection(), SampleClassWithInheritedJsonProperty.class);
assertThat(RuntimeHintsPredicates.reflection().onFieldInvocation(SampleClassWithJsonProperty.class, "privateField"))
assertThat(RuntimeHintsPredicates.reflection().onFieldAccess(SampleClassWithJsonProperty.class, "privateField"))
.accepts(this.hints);
assertThat(RuntimeHintsPredicates.reflection().onMethodInvocation(SampleClassWithJsonProperty.class, "packagePrivateMethod"))
.accepts(this.hints);

View File

@@ -381,68 +381,56 @@ class ReflectionHintsPredicatesTests {
@Test
void shouldFailForUnknownClass() {
assertThatThrownBy(() -> reflection.onFieldInvocation("com.example.DoesNotExist", "missingField"))
assertThatThrownBy(() -> reflection.onFieldAccess("com.example.DoesNotExist", "missingField"))
.isInstanceOf(ClassNotFoundException.class);
}
@Test
void fieldReflectionMatchesTypeHint() {
runtimeHints.reflection().registerType(SampleClass.class);
assertPredicateMatches(reflection.onField(SampleClass.class, "publicField"));
}
@Test
void fieldReflectionMatchesFieldHint() {
void publicFieldAccessMatchesFieldHint() {
runtimeHints.reflection().registerType(SampleClass.class, typeHint -> typeHint.withField("publicField"));
assertPredicateMatches(reflection.onField(SampleClass.class, "publicField"));
}
@Test
void fieldReflectionMatchesPublicFieldsHint() {
void publicFieldAccessMatchesPublicFieldsHint() {
runtimeHints.reflection().registerType(SampleClass.class, MemberCategory.PUBLIC_FIELDS);
assertPredicateMatches(reflection.onField(SampleClass.class, "publicField"));
}
@Test
void fieldInvocationMatchesPublicFieldsHint() {
runtimeHints.reflection().registerType(SampleClass.class, MemberCategory.INVOKE_PUBLIC_FIELDS);
assertPredicateMatches(reflection.onField(SampleClass.class, "publicField").invocation());
void publicFieldAccessMatchesAccessPublicFieldsHint() {
runtimeHints.reflection().registerType(SampleClass.class, MemberCategory.ACCESS_PUBLIC_FIELDS);
assertPredicateMatches(reflection.onField(SampleClass.class, "publicField"));
}
@Test
void fieldInvocationDoesNotMatchTypeHint() {
void fieldAccessDoesNotMatchTypeHint() {
runtimeHints.reflection().registerType(SampleClass.class);
assertPredicateDoesNotMatch(reflection.onField(SampleClass.class, "publicField").invocation());
assertPredicateDoesNotMatch(reflection.onField(SampleClass.class, "publicField"));
}
@Test
void privateFieldReflectionMatchesTypeHint() {
void privateFieldAccessDoesNotMatchTypeHint() {
runtimeHints.reflection().registerType(SampleClass.class);
assertPredicateMatches(reflection.onField(SampleClass.class, "privateField"));
assertPredicateDoesNotMatch(reflection.onField(SampleClass.class, "privateField"));
}
@Test
void privateFieldReflectionMatchesFieldHint() {
void privateFieldAccessMatchesFieldHint() {
runtimeHints.reflection().registerType(SampleClass.class, typeHint -> typeHint.withField("privateField"));
assertPredicateMatches(reflection.onField(SampleClass.class, "privateField"));
}
@Test
void privateFieldReflectionMatchesDeclaredFieldsHint() {
void privateFieldAccessMatchesDeclaredFieldsHint() {
runtimeHints.reflection().registerType(SampleClass.class, MemberCategory.DECLARED_FIELDS);
assertPredicateMatches(reflection.onField(SampleClass.class, "privateField"));
}
@Test
void privateFieldInvocationMatchesDeclaredFieldsHint() {
runtimeHints.reflection().registerType(SampleClass.class, MemberCategory.INVOKE_DECLARED_FIELDS);
assertPredicateMatches(reflection.onField(SampleClass.class, "privateField").invocation());
}
@Test
void privateFieldInvocationDoesNotMatchTypeHint() {
runtimeHints.reflection().registerType(SampleClass.class);
assertPredicateDoesNotMatch(reflection.onField(SampleClass.class, "privateField").invocation());
void privateFieldAccessMatchesAccessDeclaredFieldsHint() {
runtimeHints.reflection().registerType(SampleClass.class, MemberCategory.ACCESS_DECLARED_FIELDS);
assertPredicateMatches(reflection.onField(SampleClass.class, "privateField"));
}
}

View File

@@ -105,7 +105,7 @@ class FileNativeConfigurationWriterTests {
ReflectionHints reflectionHints = hints.reflection();
reflectionHints.registerType(StringDecoder.class, builder -> builder
.onReachableType(String.class)
.withMembers(MemberCategory.INVOKE_PUBLIC_FIELDS, MemberCategory.INVOKE_DECLARED_FIELDS,
.withMembers(MemberCategory.ACCESS_PUBLIC_FIELDS, MemberCategory.ACCESS_DECLARED_FIELDS,
MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS,
MemberCategory.INVOKE_PUBLIC_METHODS, MemberCategory.INVOKE_DECLARED_METHODS)
.withField("DEFAULT_CHARSET")

View File

@@ -82,7 +82,7 @@ class RuntimeHintsWriterTests {
RuntimeHints hints = new RuntimeHints();
hints.reflection().registerType(StringDecoder.class, builder -> builder
.onReachableType(String.class)
.withMembers(MemberCategory.INVOKE_PUBLIC_FIELDS, MemberCategory.INVOKE_DECLARED_FIELDS,
.withMembers(MemberCategory.ACCESS_PUBLIC_FIELDS, MemberCategory.ACCESS_DECLARED_FIELDS,
MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS,
MemberCategory.INVOKE_PUBLIC_METHODS, MemberCategory.INVOKE_DECLARED_METHODS,
MemberCategory.UNSAFE_ALLOCATED)