Skip to content

Commit

Permalink
Refine reflection hints handling for anonymous class
Browse files Browse the repository at this point in the history
Before this commit, anonymous classes could throw an
unexpected NullPointerException in
ReflectionsHint#registerType and lambdas entries could
be created in the related generated reflect-config.json.

This commit refines how anonymous classes are handled by
explicitly checking for null class and canonical name in
ReflectionTypeReference#of, while skipping such class in
ReflectionHints#registerType in order to keep a lenient
behavior.

Closes gh-29774
  • Loading branch information
sdeleuze committed Mar 1, 2023
1 parent fe73c63 commit dbbebf5
Show file tree
Hide file tree
Showing 7 changed files with 57 additions and 8 deletions.
Expand Up @@ -74,7 +74,7 @@ private boolean shouldSkipType(Class<?> type) {
}

private boolean shouldSkipMembers(Class<?> type) {
return (type.getCanonicalName() != null && type.getCanonicalName().startsWith("java.")) || type.isArray();
return type.getCanonicalName().startsWith("java.") || type.isArray();
}

private void registerReflectionHints(ReflectionHints hints, Set<Type> seen, Type type) {
Expand Down
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 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.
Expand Down Expand Up @@ -28,6 +28,7 @@

import org.springframework.aot.hint.TypeHint.Builder;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;

/**
Expand All @@ -36,6 +37,7 @@
* @author Stephane Nicoll
* @author Phillip Webb
* @author Andy Wilkinson
* @author Sebastien Deleuze
* @since 6.0
*/
public class ReflectionHints {
Expand Down Expand Up @@ -106,7 +108,11 @@ public ReflectionHints registerType(TypeReference type, MemberCategory... member
* @see #registerType(Class, MemberCategory...)
*/
public ReflectionHints registerType(Class<?> type, Consumer<TypeHint.Builder> typeHint) {
return registerType(TypeReference.of(type), typeHint);
Assert.notNull(type, "'type' must not be null");
if (type.getCanonicalName() != null) {
registerType(TypeReference.of(type), typeHint);
}
return this;
}

/**
Expand All @@ -117,7 +123,11 @@ public ReflectionHints registerType(Class<?> type, Consumer<TypeHint.Builder> ty
* @return {@code this}, to facilitate method chaining
*/
public ReflectionHints registerType(Class<?> type, MemberCategory... memberCategories) {
return registerType(TypeReference.of(type), memberCategories);
Assert.notNull(type, "'type' must not be null");
if (type.getCanonicalName() != null) {
registerType(TypeReference.of(type), memberCategories);
}
return this;
}

/**
Expand Down
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 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.
Expand All @@ -17,11 +17,13 @@
package org.springframework.aot.hint;

import org.springframework.lang.Nullable;
import org.springframework.util.Assert;

/**
* A {@link TypeReference} based on a {@link Class}.
*
* @author Stephane Nicoll
* @author Sebastien Deleuze
* @since 6.0
*/
final class ReflectionTypeReference extends AbstractTypeReference {
Expand All @@ -41,6 +43,8 @@ private static TypeReference getEnclosingClass(Class<?> type) {
}

static ReflectionTypeReference of(Class<?> type) {
Assert.notNull(type, "'type' must not be null");
Assert.notNull(type.getCanonicalName(), "'type.getCanonicalName()' must not be null");
return new ReflectionTypeReference(type);
}

Expand Down
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 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.
Expand All @@ -26,6 +26,7 @@
* a {@link Class} yet.
*
* @author Stephane Nicoll
* @author Sebastien Deleuze
* @since 6.0
*/
public interface TypeReference {
Expand Down Expand Up @@ -68,6 +69,7 @@ public interface TypeReference {
* Create an instance based on the specified type.
* @param type the type to wrap
* @return a type reference for the specified type
* @throws IllegalArgumentException if the specified type {@linkplain Class#getCanonicalName() canonical name} is {@code null}
*/
static TypeReference of(Class<?> type) {
return ReflectionTypeReference.of(type);
Expand Down
Expand Up @@ -34,6 +34,7 @@
* Tests for {@link ReflectionHints}.
*
* @author Stephane Nicoll
* @author Sebastien Deleuze
*/
class ReflectionHintsTests {

Expand Down Expand Up @@ -132,6 +133,16 @@ void registerField() {
assertThat(fieldHint.getName()).isEqualTo("field"));
}

@Test
void registerTypeIgnoresLambda() {
Runnable lambda = () -> { };
Consumer<TypeHint.Builder> hintBuilder = mock();
this.reflectionHints.registerType(lambda.getClass());
this.reflectionHints.registerType(lambda.getClass(), hintBuilder);
assertThat(this.reflectionHints.typeHints()).isEmpty();
verifyNoInteractions(hintBuilder);
}

private void assertTestTypeFieldHint(Consumer<FieldHint> fieldHint) {
assertThat(this.reflectionHints.typeHints()).singleElement().satisfies(typeHint -> {
assertThat(typeHint.getType().getCanonicalName()).isEqualTo(TestType.class.getCanonicalName());
Expand Down
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 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.
Expand All @@ -18,21 +18,35 @@

import java.util.stream.Stream;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.junit.jupiter.params.provider.Arguments.arguments;

/**
* Tests for {@link ReflectionTypeReference}.
*
* @author Stephane Nicoll
* @author Moritz Halbritter
* @author Sebastien Deleuze
*/
class ReflectionTypeReferenceTests {

@Test
void typeReferenceWithNullClass() {
assertThatIllegalArgumentException().isThrownBy(() -> ReflectionTypeReference.of(null));
}

@Test
void typeReferenceWithLambda() {
Runnable lambda = () -> { };
assertThatIllegalArgumentException().isThrownBy(() -> ReflectionTypeReference.of(lambda.getClass()));
}

@ParameterizedTest
@MethodSource("reflectionTargetNames")
void typeReferenceFromClassHasSuitableReflectionTargetName(Class<?> clazz, String binaryName) {
Expand Down
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 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.
Expand Down Expand Up @@ -201,6 +201,14 @@ void methodAndQueriedMethods() throws JSONException {
""", hints);
}

@Test
void ignoreLambda() throws JSONException {
Runnable anonymousRunnable = () -> { };
ReflectionHints hints = new ReflectionHints();
hints.registerType(anonymousRunnable.getClass());
assertEquals("[]", hints);
}

private void assertEquals(String expectedString, ReflectionHints hints) throws JSONException {
StringWriter out = new StringWriter();
BasicJsonWriter writer = new BasicJsonWriter(out, "\t");
Expand Down

0 comments on commit dbbebf5

Please sign in to comment.