Skip to content

Commit

Permalink
Generate hints for nested generics in configuration properties
Browse files Browse the repository at this point in the history
  • Loading branch information
mhalbritter committed Jul 21, 2022
1 parent 57dc274 commit 35c49af
Show file tree
Hide file tree
Showing 2 changed files with 116 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -163,29 +163,51 @@ private boolean isSetterMandatory(String propertyName, ResolvableType propertyTy
if (propertyClass == null) {
return true;
}
if (getComponentType(propertyType) != null) {
if (isContainer(propertyType)) {
return false;
}
return !isNestedType(propertyName, propertyClass);
}

private Class<?> getComponentType(ResolvableType propertyType) {
Class<?> propertyClass = propertyType.toClass();
ResolvableType componentType = null;
if (propertyType.isArray()) {
return propertyType.getComponentType().toClass();
componentType = propertyType.getComponentType();
}
else if (Collection.class.isAssignableFrom(propertyClass)) {
return propertyType.as(Collection.class).getGeneric(0).toClass();
componentType = propertyType.asCollection().getGeneric(0);
}
else if (Map.class.isAssignableFrom(propertyClass)) {
return propertyType.as(Map.class).getGeneric(1).toClass();
componentType = propertyType.asMap().getGeneric(1);
}
return null;
if (componentType == null) {
return null;
}
if (isContainer(componentType)) {
// Resolve nested generics like Map<String, List<SomeType>>
return getComponentType(componentType);
}
return componentType.toClass();
}

private boolean isContainer(ResolvableType type) {
if (type.isArray()) {
return true;
}
if (Collection.class.isAssignableFrom(type.toClass())) {
return true;
}
else if (Map.class.isAssignableFrom(type.toClass())) {
return true;
}
return false;
}

/**
* Specify whether the specified property refer to a nested type. A nested type
* represents a sub-namespace that need to be fully resolved.
* represents a sub-namespace that need to be fully resolved. Nested types are either
* inner classes or annotated with {@link NestedConfigurationProperty}.
* @param propertyName the name of the property
* @param propertyType the type of the property
* @return whether the specified {@code propertyType} is a nested type
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import java.lang.reflect.Constructor;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
Expand All @@ -34,6 +35,7 @@
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.TypeHint;
import org.springframework.aot.hint.TypeReference;
import org.springframework.aot.hint.predicate.RuntimeHintsPredicates;
import org.springframework.beans.factory.aot.AotFactoriesLoader;
import org.springframework.beans.factory.aot.BeanFactoryInitializationAotContribution;
import org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor;
Expand All @@ -53,6 +55,7 @@
* Tests for {@link ConfigurationPropertiesBeanFactoryInitializationAotProcessor}.
*
* @author Stephane Nicoll
* @author Moritz Halbritter
*/
class ConfigurationPropertiesBeanFactoryInitializationAotProcessorTests {

Expand Down Expand Up @@ -227,6 +230,31 @@ void processConfigurationPropertiesWithUnresolvedGeneric() {
.anySatisfy(javaBeanBinding(GenericObject.class));
}

@Test
void processConfigurationPropertiesWithNestedGenerics() {
RuntimeHints runtimeHints = process(NestedGenerics.class);
assertThat(RuntimeHintsPredicates.reflection().onType(NestedGenerics.class)
.withMemberCategories(MemberCategory.INVOKE_DECLARED_METHODS, MemberCategory.INVOKE_PUBLIC_METHODS))
.accepts(runtimeHints);
assertThat(RuntimeHintsPredicates.reflection().onType(NestedGenerics.Nested.class)
.withMemberCategories(MemberCategory.INVOKE_DECLARED_METHODS, MemberCategory.INVOKE_PUBLIC_METHODS))
.accepts(runtimeHints);
}

@Test
void processConfigurationPropertiesWithMultipleNestedClasses() {
RuntimeHints runtimeHints = process(TripleNested.class);
assertThat(RuntimeHintsPredicates.reflection().onType(TripleNested.class)
.withMemberCategories(MemberCategory.INVOKE_DECLARED_METHODS, MemberCategory.INVOKE_PUBLIC_METHODS))
.accepts(runtimeHints);
assertThat(RuntimeHintsPredicates.reflection().onType(TripleNested.DoubleNested.class)
.withMemberCategories(MemberCategory.INVOKE_DECLARED_METHODS, MemberCategory.INVOKE_PUBLIC_METHODS))
.accepts(runtimeHints);
assertThat(RuntimeHintsPredicates.reflection().onType(TripleNested.DoubleNested.Nested.class)
.withMemberCategories(MemberCategory.INVOKE_DECLARED_METHODS, MemberCategory.INVOKE_PUBLIC_METHODS))
.accepts(runtimeHints);
}

private Consumer<TypeHint> javaBeanBinding(Class<?> type) {
return javaBeanBinding(type, type.getDeclaredConstructors()[0]);
}
Expand Down Expand Up @@ -590,4 +618,64 @@ public T getValue() {

}

@ConfigurationProperties(prefix = "nested-generics")
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 String field;

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

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

}

}

@ConfigurationProperties(prefix = "triple-nested")
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 String field;

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

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

}

}

}

}

0 comments on commit 35c49af

Please sign in to comment.