|
|
|
|
@ -1,5 +1,5 @@
@@ -1,5 +1,5 @@
|
|
|
|
|
/* |
|
|
|
|
* Copyright 2012-2023 the original author or authors. |
|
|
|
|
* Copyright 2012-2025 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. |
|
|
|
|
@ -17,16 +17,23 @@
@@ -17,16 +17,23 @@
|
|
|
|
|
package org.springframework.boot.configurationprocessor; |
|
|
|
|
|
|
|
|
|
import java.time.Duration; |
|
|
|
|
import java.util.Map; |
|
|
|
|
import java.util.function.BiConsumer; |
|
|
|
|
|
|
|
|
|
import javax.lang.model.element.TypeElement; |
|
|
|
|
import javax.lang.model.element.VariableElement; |
|
|
|
|
import javax.lang.model.type.TypeMirror; |
|
|
|
|
import javax.lang.model.util.ElementFilter; |
|
|
|
|
|
|
|
|
|
import org.junit.jupiter.api.Test; |
|
|
|
|
|
|
|
|
|
import org.springframework.boot.configurationprocessor.TypeUtils.TypeDescriptor; |
|
|
|
|
import org.springframework.boot.configurationprocessor.test.RoundEnvironmentTester; |
|
|
|
|
import org.springframework.boot.configurationprocessor.test.TestableAnnotationProcessor; |
|
|
|
|
import org.springframework.boot.configurationsample.generic.AbstractGenericProperties; |
|
|
|
|
import org.springframework.boot.configurationsample.generic.AbstractIntermediateGenericProperties; |
|
|
|
|
import org.springframework.boot.configurationsample.generic.MixGenericNameProperties; |
|
|
|
|
import org.springframework.boot.configurationsample.generic.SimpleGenericProperties; |
|
|
|
|
import org.springframework.boot.configurationsample.generic.UnresolvedGenericProperties; |
|
|
|
|
import org.springframework.core.test.tools.SourceFile; |
|
|
|
|
import org.springframework.core.test.tools.TestCompiler; |
|
|
|
|
|
|
|
|
|
@ -41,40 +48,52 @@ import static org.assertj.core.api.Assertions.assertThat;
@@ -41,40 +48,52 @@ import static org.assertj.core.api.Assertions.assertThat;
|
|
|
|
|
class TypeUtilsTests { |
|
|
|
|
|
|
|
|
|
@Test |
|
|
|
|
void resolveTypeDescriptorOnConcreteClass() { |
|
|
|
|
void resolveTypeOnConcreteClass() { |
|
|
|
|
process(SimpleGenericProperties.class, (roundEnv, typeUtils) -> { |
|
|
|
|
TypeDescriptor typeDescriptor = typeUtils |
|
|
|
|
.resolveTypeDescriptor(roundEnv.getRootElement(SimpleGenericProperties.class)); |
|
|
|
|
assertThat(typeDescriptor.getGenerics().keySet().stream().map(Object::toString)).containsOnly("A", "B", |
|
|
|
|
"C"); |
|
|
|
|
assertThat(typeDescriptor.resolveGeneric("A")).hasToString(String.class.getName()); |
|
|
|
|
assertThat(typeDescriptor.resolveGeneric("B")).hasToString(Integer.class.getName()); |
|
|
|
|
assertThat(typeDescriptor.resolveGeneric("C")).hasToString(Duration.class.getName()); |
|
|
|
|
|
|
|
|
|
TypeElement typeElement = roundEnv.getRootElement(SimpleGenericProperties.class); |
|
|
|
|
assertThat(getTypeOfField(typeUtils, typeElement, "name")).hasToString(String.class.getName()); |
|
|
|
|
assertThat(getTypeOfField(typeUtils, typeElement, "mappings")) |
|
|
|
|
.hasToString(constructMapType(Integer.class, Duration.class)); |
|
|
|
|
}); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@Test |
|
|
|
|
void resolveTypeDescriptorOnIntermediateClass() { |
|
|
|
|
void resolveTypeOnIntermediateClass() { |
|
|
|
|
process(AbstractIntermediateGenericProperties.class, (roundEnv, typeUtils) -> { |
|
|
|
|
TypeDescriptor typeDescriptor = typeUtils |
|
|
|
|
.resolveTypeDescriptor(roundEnv.getRootElement(AbstractIntermediateGenericProperties.class)); |
|
|
|
|
assertThat(typeDescriptor.getGenerics().keySet().stream().map(Object::toString)).containsOnly("A", "B", |
|
|
|
|
"C"); |
|
|
|
|
assertThat(typeDescriptor.resolveGeneric("A")).hasToString(String.class.getName()); |
|
|
|
|
assertThat(typeDescriptor.resolveGeneric("B")).hasToString(Integer.class.getName()); |
|
|
|
|
assertThat(typeDescriptor.resolveGeneric("C")).hasToString("C"); |
|
|
|
|
TypeElement typeElement = roundEnv.getRootElement(AbstractIntermediateGenericProperties.class); |
|
|
|
|
assertThat(getTypeOfField(typeUtils, typeElement, "name")).hasToString(String.class.getName()); |
|
|
|
|
assertThat(getTypeOfField(typeUtils, typeElement, "mappings")) |
|
|
|
|
.hasToString(constructMapType(Integer.class, Object.class)); |
|
|
|
|
}); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@Test |
|
|
|
|
void resolveTypeDescriptorWithOnlyGenerics() { |
|
|
|
|
void resolveTypeWithOnlyGenerics() { |
|
|
|
|
process(AbstractGenericProperties.class, (roundEnv, typeUtils) -> { |
|
|
|
|
TypeDescriptor typeDescriptor = typeUtils |
|
|
|
|
.resolveTypeDescriptor(roundEnv.getRootElement(AbstractGenericProperties.class)); |
|
|
|
|
assertThat(typeDescriptor.getGenerics().keySet().stream().map(Object::toString)).containsOnly("A", "B", |
|
|
|
|
"C"); |
|
|
|
|
TypeElement typeElement = roundEnv.getRootElement(AbstractGenericProperties.class); |
|
|
|
|
assertThat(getTypeOfField(typeUtils, typeElement, "name")).hasToString(Object.class.getName()); |
|
|
|
|
assertThat(getTypeOfField(typeUtils, typeElement, "mappings")) |
|
|
|
|
.hasToString(constructMapType(Object.class, Object.class)); |
|
|
|
|
}); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@Test |
|
|
|
|
void resolveTypeWithUnresolvedGenericProperties() { |
|
|
|
|
process(UnresolvedGenericProperties.class, (roundEnv, typeUtils) -> { |
|
|
|
|
TypeElement typeElement = roundEnv.getRootElement(UnresolvedGenericProperties.class); |
|
|
|
|
assertThat(getTypeOfField(typeUtils, typeElement, "name")).hasToString(String.class.getName()); |
|
|
|
|
assertThat(getTypeOfField(typeUtils, typeElement, "mappings")) |
|
|
|
|
.hasToString(constructMapType(Number.class, Object.class)); |
|
|
|
|
}); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@Test |
|
|
|
|
void resolvedTypeMixGenericNamePropertiesProperties() { |
|
|
|
|
process(MixGenericNameProperties.class, (roundEnv, typeUtils) -> { |
|
|
|
|
TypeElement typeElement = roundEnv.getRootElement(MixGenericNameProperties.class); |
|
|
|
|
assertThat(getTypeOfField(typeUtils, typeElement, "name")).hasToString(String.class.getName()); |
|
|
|
|
assertThat(getTypeOfField(typeUtils, typeElement, "mappings")) |
|
|
|
|
.hasToString(constructMapType(Number.class, Object.class)); |
|
|
|
|
}); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@ -87,4 +106,29 @@ class TypeUtilsTests {
@@ -87,4 +106,29 @@ class TypeUtilsTests {
|
|
|
|
|
}); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private String constructMapType(Class<?> keyType, Class<?> valueType) { |
|
|
|
|
return "%s<%s,%s>".formatted(Map.class.getName(), keyType.getName(), valueType.getName()); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private String getTypeOfField(TypeUtils typeUtils, TypeElement typeElement, String name) { |
|
|
|
|
TypeMirror field = findField(typeUtils, typeElement, name); |
|
|
|
|
if (field == null) { |
|
|
|
|
throw new IllegalStateException("Unable to find field '" + name + "' in " + typeElement); |
|
|
|
|
} |
|
|
|
|
return typeUtils.getType(typeElement, field); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private TypeMirror findField(TypeUtils typeUtils, TypeElement typeElement, String name) { |
|
|
|
|
for (VariableElement variableElement : ElementFilter.fieldsIn(typeElement.getEnclosedElements())) { |
|
|
|
|
if (variableElement.getSimpleName().contentEquals(name)) { |
|
|
|
|
return variableElement.asType(); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
TypeMirror superclass = typeElement.getSuperclass(); |
|
|
|
|
if (superclass != null && !superclass.toString().equals(Object.class.getName())) { |
|
|
|
|
return findField(typeUtils, (TypeElement) typeUtils.asElement(superclass), name); |
|
|
|
|
} |
|
|
|
|
return null; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|