Home / Class/ BindableRuntimeHintsRegistrarTests Class — spring-boot Architecture

BindableRuntimeHintsRegistrarTests Class — spring-boot Architecture

Architecture documentation for the BindableRuntimeHintsRegistrarTests class in BindableRuntimeHintsRegistrarTests.java from the spring-boot codebase.

Entity Profile

Relationship Graph

Source Code

core/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/BindableRuntimeHintsRegistrarTests.java lines 60–854

class BindableRuntimeHintsRegistrarTests {

	@Test
	void registerHints() {
		RuntimeHints runtimeHints = new RuntimeHints();
		Class<?>[] types = { BoundConfigurationProperties.class, ConfigurationPropertiesBean.class };
		BindableRuntimeHintsRegistrar registrar = new BindableRuntimeHintsRegistrar(types);
		registrar.registerHints(runtimeHints);
		for (Class<?> type : types) {
			assertThat(RuntimeHintsPredicates.reflection().onType(type)).accepts(runtimeHints);
		}
	}

	@Test
	void registerHintsWithIterable() {
		RuntimeHints runtimeHints = new RuntimeHints();
		List<Class<?>> types = Arrays.asList(BoundConfigurationProperties.class, ConfigurationPropertiesBean.class);
		BindableRuntimeHintsRegistrar registrar = BindableRuntimeHintsRegistrar.forTypes(types);
		registrar.registerHints(runtimeHints);
		for (Class<?> type : types) {
			assertThat(RuntimeHintsPredicates.reflection().onType(type)).accepts(runtimeHints);
		}
	}

	@Test
	void registerHintsWhenNoClasses() {
		RuntimeHints runtimeHints = new RuntimeHints();
		BindableRuntimeHintsRegistrar registrar = new BindableRuntimeHintsRegistrar(new Class<?>[0]);
		registrar.registerHints(runtimeHints);
		assertThat(runtimeHints.reflection().typeHints()).isEmpty();
	}

	@Test
	void registerHintsViaForType() {
		RuntimeHints runtimeHints = new RuntimeHints();
		Class<?>[] types = { BoundConfigurationProperties.class, ConfigurationPropertiesBean.class };
		BindableRuntimeHintsRegistrar registrar = BindableRuntimeHintsRegistrar.forTypes(types);
		registrar.registerHints(runtimeHints);
		for (Class<?> type : types) {
			assertThat(RuntimeHintsPredicates.reflection().onType(type)).accepts(runtimeHints);
		}
	}

	@Test
	void registerHintsWhenJavaBean() {
		RuntimeHints runtimeHints = registerHints(JavaBean.class);
		assertThat(runtimeHints.reflection().typeHints()).singleElement().satisfies(javaBeanBinding(JavaBean.class));
	}

	@Test
	void registerHintsWhenJavaBeanWithSeveralConstructors() throws NoSuchMethodException {
		RuntimeHints runtimeHints = registerHints(WithSeveralConstructors.class);
		assertThat(runtimeHints.reflection().typeHints()).singleElement()
			.satisfies(javaBeanBinding(WithSeveralConstructors.class)
				.constructor(WithSeveralConstructors.class.getDeclaredConstructor()));
	}

	@Test
	void registerHintsWhenJavaBeanWithMapOfPojo() {
		RuntimeHints runtimeHints = registerHints(WithMap.class);
		assertThat(runtimeHints.reflection().typeHints()).hasSize(2)
			.anySatisfy(javaBeanBinding(WithMap.class).methods("getAddresses"))
			.anySatisfy(javaBeanBinding(Address.class));
	}

	@Test
	void registerHintsWhenJavaBeanWithListOfPojo() {
		RuntimeHints runtimeHints = registerHints(WithList.class);
		assertThat(runtimeHints.reflection().typeHints()).hasSize(2)
			.anySatisfy(javaBeanBinding(WithList.class).methods("getAllAddresses"))
			.anySatisfy(javaBeanBinding(Address.class));
	}

	@Test
	void registerHintsWhenJavaBeanWitArrayOfPojo() {
		RuntimeHints runtimeHints = registerHints(WithArray.class);
		assertThat(runtimeHints.reflection().typeHints()).hasSize(2)
			.anySatisfy(javaBeanBinding(WithArray.class).methods("getAllAddresses"))
			.anySatisfy(javaBeanBinding(Address.class));
	}

	@Test
	void registerHintsWhenJavaBeanWithListOfJavaType() {
		RuntimeHints runtimeHints = registerHints(WithSimpleList.class);
		assertThat(runtimeHints.reflection().typeHints()).singleElement()
			.satisfies(javaBeanBinding(WithSimpleList.class).methods("getNames"));
	}

	@Test
	void registerHintsWhenValueObject() {
		RuntimeHints runtimeHints = registerHints(Immutable.class);
		assertThat(runtimeHints.reflection().typeHints()).singleElement()
			.satisfies(valueObjectBinding(Immutable.class));
	}

	@Test
	void registerHintsWhenValueObjectWithSpecificConstructor() throws NoSuchMethodException {
		RuntimeHints runtimeHints = registerHints(ImmutableWithSeveralConstructors.class);
		assertThat(runtimeHints.reflection().typeHints()).singleElement()
			.satisfies(valueObjectBinding(ImmutableWithSeveralConstructors.class,
					ImmutableWithSeveralConstructors.class.getDeclaredConstructor(String.class)));
	}

	@Test
	void registerHintsWhenValueObjectWithSeveralLayersOfPojo() {
		RuntimeHints runtimeHints = registerHints(ImmutableWithList.class);
		assertThat(runtimeHints.reflection().typeHints()).hasSize(3)
			.anySatisfy(valueObjectBinding(ImmutableWithList.class))
			.anySatisfy(valueObjectBinding(Person.class))
			.anySatisfy(valueObjectBinding(Address.class));
	}

	@Test
	void registerHintsWhenHasNestedTypeNotUsedIsIgnored() {
		RuntimeHints runtimeHints = registerHints(WithNested.class);
		assertThat(runtimeHints.reflection().typeHints()).singleElement().satisfies(javaBeanBinding(WithNested.class));
	}

	@Test
	void registerHintsWhenWhenHasNestedExternalType() {
		RuntimeHints runtimeHints = registerHints(WithExternalNested.class);
		assertThat(runtimeHints.reflection().typeHints()).hasSize(3)
			.anySatisfy(javaBeanBinding(WithExternalNested.class)
				.methods("getName", "setName", "getSampleType", "setSampleType")
				.fields("name", "sampleType"))
			.anySatisfy(javaBeanBinding(SampleType.class).methods("getNested").fields("nested"))
			.anySatisfy(javaBeanBinding(SampleType.Nested.class));
	}

	@Test
	void registerHintsWhenHasRecursiveType() {
		RuntimeHints runtimeHints = registerHints(WithRecursive.class);
		assertThat(runtimeHints.reflection().typeHints()).hasSize(2)
			.anySatisfy(
					javaBeanBinding(WithRecursive.class).methods("getRecursive", "setRecursive").fields("recursive"))
			.anySatisfy(javaBeanBinding(Recursive.class).methods("getRecursive", "setRecursive").fields("recursive"));
	}

	@Test
	void registerHintsWhenValueObjectWithRecursiveType() {
		RuntimeHints runtimeHints = registerHints(ImmutableWithRecursive.class);
		assertThat(runtimeHints.reflection().typeHints()).hasSize(2)
			.anySatisfy(valueObjectBinding(ImmutableWithRecursive.class))
			.anySatisfy(valueObjectBinding(ImmutableRecursive.class));
	}

	@Test
	void registerHintsWhenHasWellKnownTypes() {
		RuntimeHints runtimeHints = registerHints(WithWellKnownTypes.class);
		assertThat(runtimeHints.reflection().typeHints()).singleElement()
			.satisfies(javaBeanBinding(WithWellKnownTypes.class)
				.methods("getApplicationContext", "setApplicationContext", "getEnvironment", "setEnvironment")
				.fields("applicationContext", "environment"));
	}

	@Test
	void registerHintsWhenHasCrossReference() {
		RuntimeHints runtimeHints = registerHints(WithCrossReference.class);
		assertThat(runtimeHints.reflection().typeHints()).hasSize(3)
			.anySatisfy(javaBeanBinding(WithCrossReference.class).methods("getCrossReferenceA", "setCrossReferenceA")
				.fields("crossReferenceA"))
			.anySatisfy(javaBeanBinding(CrossReferenceA.class).methods("getCrossReferenceB", "setCrossReferenceB")
				.fields("crossReferenceB"))
			.anySatisfy(javaBeanBinding(CrossReferenceB.class).methods("getCrossReferenceA", "setCrossReferenceA")
				.fields("crossReferenceA"));
	}

	@Test
	void registerHintsWhenHasUnresolvedGeneric() {
		RuntimeHints runtimeHints = registerHints(WithGeneric.class);
		assertThat(runtimeHints.reflection().typeHints()).hasSize(2)
			.anySatisfy(javaBeanBinding(WithGeneric.class).methods("getGeneric").fields("generic"))
			.anySatisfy(javaBeanBinding(GenericObject.class));
	}

	@Test
	void registerHintsWhenHasNestedGenerics() {
		RuntimeHints runtimeHints = registerHints(NestedGenerics.class);
		assertThat(runtimeHints.reflection().typeHints()).hasSize(2);
		assertThat(RuntimeHintsPredicates.reflection().onType(NestedGenerics.class)).accepts(runtimeHints);
		assertThat(RuntimeHintsPredicates.reflection().onType(NestedGenerics.Nested.class)).accepts(runtimeHints);
	}

	@Test
	void registerHintsWhenHasMultipleNestedClasses() {
		RuntimeHints runtimeHints = registerHints(TripleNested.class);
		assertThat(runtimeHints.reflection().typeHints()).hasSize(3);
		assertThat(RuntimeHintsPredicates.reflection().onType(TripleNested.class)).accepts(runtimeHints);
		assertThat(RuntimeHintsPredicates.reflection().onType(TripleNested.DoubleNested.class)).accepts(runtimeHints);
		assertThat(RuntimeHintsPredicates.reflection().onType(TripleNested.DoubleNested.Nested.class))
			.accepts(runtimeHints);
	}

	@Test
	void registerHintsWhenHasPackagePrivateGettersAndSetters() {
		RuntimeHints runtimeHints = registerHints(PackagePrivateGettersAndSetters.class);
		assertThat(runtimeHints.reflection().typeHints()).singleElement()
			.satisfies(javaBeanBinding(PackagePrivateGettersAndSetters.class)
				.methods("getAlpha", "setAlpha", "getBravo", "setBravo")
				.fields("alpha", "bravo"));
	}

	@Test
	void registerHintsWhenHasInheritedNestedProperties() {
		RuntimeHints runtimeHints = registerHints(ExtendingProperties.class);
		assertThat(runtimeHints.reflection().typeHints()).hasSize(3);
		assertThat(runtimeHints.reflection().getTypeHint(BaseProperties.class)).satisfies((entry) -> {
			assertThat(entry.getMemberCategories()).isEmpty();
			assertThat(entry.methods()).extracting(ExecutableHint::getName)
				.containsExactlyInAnyOrder("getInheritedNested", "setInheritedNested");
		});
		assertThat(runtimeHints.reflection().getTypeHint(ExtendingProperties.class))
			.satisfies(javaBeanBinding(ExtendingProperties.class).methods("getBravo", "setBravo").fields("bravo"));
		assertThat(runtimeHints.reflection().getTypeHint(InheritedNested.class))
			.satisfies(javaBeanBinding(InheritedNested.class).methods("getAlpha", "setAlpha").fields("alpha"));
	}

	@Test
	void registerHintsWhenHasComplexNestedProperties() {
		RuntimeHints runtimeHints = registerHints(ComplexNestedProperties.class);
		assertThat(runtimeHints.reflection().typeHints()).hasSize(4);
		assertThat(runtimeHints.reflection().getTypeHint(Retry.class)).satisfies((entry) -> {
			assertThat(entry.getMemberCategories()).isEmpty();
			assertThat(entry.methods()).extracting(ExecutableHint::getName)
				.containsExactlyInAnyOrder("getCount", "setCount");
		});
		assertThat(runtimeHints.reflection().getTypeHint(ListenerRetry.class))
			.satisfies(javaBeanBinding(ListenerRetry.class).methods("isStateless", "setStateless").fields("stateless"));
		assertThat(runtimeHints.reflection().getTypeHint(Simple.class))
			.satisfies(javaBeanBinding(Simple.class).methods("getRetry").fields("retry"));
		assertThat(runtimeHints.reflection().getTypeHint(ComplexNestedProperties.class))
			.satisfies(javaBeanBinding(ComplexNestedProperties.class).methods("getSimple").fields("simple"));
	}

	@Test
	void registerHintsDoesNotThrowWhenParameterInformationForConstructorBindingIsNotAvailable()
			throws NoSuchMethodException, SecurityException {
		Constructor<?> constructor = PoolProperties.InterceptorProperty.class.getConstructor(String.class,
				String.class);
		@Nullable String[] parameterNames = new StandardReflectionParameterNameDiscoverer().getParameterNames(constructor);
		assertThat(parameterNames).isNull();
		assertThatNoException().isThrownBy(() -> registerHints(PoolProperties.class));
	}

	private JavaBeanBinding javaBeanBinding(Class<?> type) {
		return new JavaBeanBinding(type);
	}

	private Consumer<TypeHint> valueObjectBinding(Class<?> type) {
		return valueObjectBinding(type, type.getDeclaredConstructors()[0]);
	}

	private Consumer<TypeHint> valueObjectBinding(Class<?> type, Constructor<?> constructor) {
		return (entry) -> {
			assertThat(entry.getType()).isEqualTo(TypeReference.of(type));
			assertThat(entry.constructors()).singleElement().satisfies(match(constructor));
			assertThat(entry.getMemberCategories()).isEmpty();
			assertThat(entry.methods()).isEmpty();
		};
	}

	private static Consumer<ExecutableHint> match(Constructor<?> constructor) {
		return (executableHint) -> {
			assertThat(executableHint.getName()).isEqualTo("<init>");
			assertThat(Arrays.stream(constructor.getParameterTypes()).map(TypeReference::of).toList())
				.isEqualTo(executableHint.getParameterTypes());
		};
	}

	private RuntimeHints registerHints(Class<?>... types) {
		RuntimeHints hints = new RuntimeHints();
		BindableRuntimeHintsRegistrar.forTypes(types).registerHints(hints);
		return hints;
	}

	public static class JavaBean {

	}

	public static class WithSeveralConstructors {

		WithSeveralConstructors() {
		}

		WithSeveralConstructors(String ignored) {
		}

	}

	public static class WithMap {

		public Map<String, Address> getAddresses() {
			return Collections.emptyMap();
		}

	}

	public static class WithList {

		public List<Address> getAllAddresses() {
			return Collections.emptyList();
		}

	}

	public static class WithSimpleList {

		public List<String> getNames() {
			return Collections.emptyList();
		}

	}

	public static class WithArray {

		public Address[] getAllAddresses() {
			return new Address[0];
		}

	}

	public static class Immutable {

		@SuppressWarnings("unused")
		private final String name;

		Immutable(String name) {
			this.name = name;
		}

	}

	public static class ImmutableWithSeveralConstructors {

		@SuppressWarnings("unused")
		private final String name;

		@ConstructorBinding
		ImmutableWithSeveralConstructors(String name) {
			this.name = name;
		}

		ImmutableWithSeveralConstructors() {
			this("test");
		}

	}

	public static class ImmutableWithList {

		@SuppressWarnings("unused")
		private final List<Person> family;

		ImmutableWithList(List<Person> family) {
			this.family = family;
		}

	}

	public static class WithNested {

		static class OneLevelDown {

		}

	}

	public static class WithExternalNested {

		private @Nullable String name;

		@NestedConfigurationProperty
		private @Nullable SampleType sampleType;

		public @Nullable String getName() {
			return this.name;
		}

		public void setName(@Nullable String name) {
			this.name = name;
		}

		public @Nullable SampleType getSampleType() {
			return this.sampleType;
		}

		public void setSampleType(@Nullable SampleType sampleType) {
			this.sampleType = sampleType;
		}

	}

	public static class WithRecursive {

		@NestedConfigurationProperty
		private @Nullable Recursive recursive;

		public @Nullable Recursive getRecursive() {
			return this.recursive;
		}

		public void setRecursive(@Nullable Recursive recursive) {
			this.recursive = recursive;
		}

	}

	public static class ImmutableWithRecursive {

		@NestedConfigurationProperty
		private final @Nullable ImmutableRecursive recursive;

		ImmutableWithRecursive(@Nullable ImmutableRecursive recursive) {
			this.recursive = recursive;
		}

	}

	public static class WithWellKnownTypes implements ApplicationContextAware, EnvironmentAware {

		@SuppressWarnings("NullAway.Init")
		private ApplicationContext applicationContext;

		@SuppressWarnings("NullAway.Init")
		private Environment environment;

		public ApplicationContext getApplicationContext() {
			return this.applicationContext;
		}

		@Override
		public void setApplicationContext(ApplicationContext applicationContext) {
			this.applicationContext = applicationContext;
		}

		public Environment getEnvironment() {
			return this.environment;
		}

		@Override
		public void setEnvironment(Environment environment) {
			this.environment = environment;
		}

	}

	public static class SampleType {

		private final Nested nested = new Nested();

		public Nested getNested() {
			return this.nested;
		}

		static class Nested {

		}

	}

	public static class PackagePrivateGettersAndSetters {

		private @Nullable String alpha;

		private @Nullable Map<String, String> bravo;

		@Nullable String getAlpha() {
			return this.alpha;
		}

		void setAlpha(@Nullable String alpha) {
			this.alpha = alpha;
		}

		@Nullable Map<String, String> getBravo() {
			return this.bravo;
		}

		void setBravo(@Nullable Map<String, String> bravo) {
			this.bravo = bravo;
		}

	}

	public static class Address {

	}

	public static class Person {

		@SuppressWarnings("unused")
		private final String firstName;

		@SuppressWarnings("unused")
		private final String lastName;

		@NestedConfigurationProperty
		private final Address address;

		Person(String firstName, String lastName, Address address) {
			this.firstName = firstName;
			this.lastName = lastName;
			this.address = address;
		}

	}

	public static class Recursive {

		private @Nullable Recursive recursive;

		public @Nullable Recursive getRecursive() {
			return this.recursive;
		}

		public void setRecursive(@Nullable Recursive recursive) {
			this.recursive = recursive;
		}

	}

	public static class ImmutableRecursive {

		@SuppressWarnings("unused")
		private final @Nullable ImmutableRecursive recursive;

		ImmutableRecursive(@Nullable ImmutableRecursive recursive) {
			this.recursive = recursive;
		}

	}

	public static class WithCrossReference {

		@NestedConfigurationProperty
		private @Nullable CrossReferenceA crossReferenceA;

		public void setCrossReferenceA(@Nullable CrossReferenceA crossReferenceA) {
			this.crossReferenceA = crossReferenceA;
		}

		public @Nullable CrossReferenceA getCrossReferenceA() {
			return this.crossReferenceA;
		}

	}

	public static class CrossReferenceA {

		@NestedConfigurationProperty
		private @Nullable CrossReferenceB crossReferenceB;

		public void setCrossReferenceB(@Nullable CrossReferenceB crossReferenceB) {
			this.crossReferenceB = crossReferenceB;
		}

		public @Nullable CrossReferenceB getCrossReferenceB() {
			return this.crossReferenceB;
		}

	}

	public static class CrossReferenceB {

		private @Nullable CrossReferenceA crossReferenceA;

		public void setCrossReferenceA(@Nullable CrossReferenceA crossReferenceA) {
			this.crossReferenceA = crossReferenceA;
		}

		public @Nullable CrossReferenceA getCrossReferenceA() {
			return this.crossReferenceA;
		}

	}

	public static class WithGeneric {

		@NestedConfigurationProperty
		private @Nullable GenericObject<?> generic;

		public @Nullable GenericObject<?> getGeneric() {
			return this.generic;
		}

	}

	public static final class GenericObject<T> {

		private final T value;

		GenericObject(T value) {
			this.value = value;
		}

		public T getValue() {
			return this.value;
		}

	}

	public static class NestedGenerics {

		private final Map<String, List<Nested>> nested = new HashMap<>();

		public Map<String, List<Nested>> getNested() {
			return this.nested;
		}

		public static class Nested {

			private @Nullable String field;

			public @Nullable String getField() {
				return this.field;
			}

			public void setField(@Nullable String field) {
				this.field = field;
			}

		}

	}

	public static class TripleNested {

		private final DoubleNested doubleNested = new DoubleNested();

		public DoubleNested getDoubleNested() {
			return this.doubleNested;
		}

		public static class DoubleNested {

			private final Nested nested = new Nested();

			public Nested getNested() {
				return this.nested;
			}

			public static class Nested {

				private @Nullable String field;

				public @Nullable String getField() {
					return this.field;
				}

				public void setField(@Nullable String field) {
					this.field = field;
				}

			}

		}

	}

	public abstract static class BaseProperties {

		private @Nullable InheritedNested inheritedNested;

		public @Nullable InheritedNested getInheritedNested() {
			return this.inheritedNested;
		}

		public void setInheritedNested(@Nullable InheritedNested inheritedNested) {
			this.inheritedNested = inheritedNested;
		}

		public static class InheritedNested {

			private @Nullable String alpha;

			public @Nullable String getAlpha() {
				return this.alpha;
			}

			public void setAlpha(@Nullable String alpha) {
				this.alpha = alpha;
			}

		}

	}

	public static class ExtendingProperties extends BaseProperties {

		private @Nullable String bravo;

		public @Nullable String getBravo() {
			return this.bravo;
		}

		public void setBravo(@Nullable String bravo) {
			this.bravo = bravo;
		}

	}

	public static class ComplexNestedProperties {

		private final Simple simple = new Simple();

		public Simple getSimple() {
			return this.simple;
		}

		public static class Simple {

			private final ListenerRetry retry = new ListenerRetry();

			public ListenerRetry getRetry() {
				return this.retry;
			}

		}

		public abstract static class Retry {

			private int count = 5;

			public int getCount() {
				return this.count;
			}

			public void setCount(int count) {
				this.count = count;
			}

		}

		public static class ListenerRetry extends Retry {

			private boolean stateless;

			public boolean isStateless() {
				return this.stateless;
			}

			public void setStateless(boolean stateless) {
				this.stateless = stateless;
			}

		}

	}

	private static final class JavaBeanBinding implements Consumer<TypeHint> {

		private final Class<?> type;

		private Constructor<?> constructor;

		private List<String> expectedMethods = Collections.emptyList();

		private List<String> expectedFields = Collections.emptyList();

		private JavaBeanBinding(Class<?> type) {
			this.type = type;
			this.constructor = this.type.getDeclaredConstructors()[0];
		}

		@Override
		public void accept(TypeHint entry) {
			assertThat(entry.getType()).isEqualTo(TypeReference.of(this.type));
			assertThat(entry.constructors()).singleElement().satisfies(match(this.constructor));
			assertThat(entry.getMemberCategories()).isEmpty();
			assertThat(entry.methods()).as("Methods requiring reflection")
				.extracting(ExecutableHint::getName)
				.containsExactlyInAnyOrderElementsOf(this.expectedMethods);
			assertThat(entry.fields()).as("Fields requiring reflection")
				.extracting(FieldHint::getName)
				.containsExactlyInAnyOrderElementsOf(this.expectedFields);
		}

		private JavaBeanBinding constructor(Constructor<?> constructor) {
			this.constructor = constructor;
			return this;
		}

		private JavaBeanBinding methods(String... methods) {
			this.expectedMethods = List.of(methods);
			return this;
		}

		private JavaBeanBinding fields(String... fields) {
			this.expectedFields = List.of(fields);
			return this;
		}

	}

}

Domain

Analyze Your Own Codebase

Get architecture documentation, dependency graphs, and domain analysis for your codebase in minutes.

Try Supermodel Free