Skip to content

Commit e29dc45

Browse files
committed
Merge branch '3.3.x' into 3.4.x
Closes gh-45039
2 parents 3f1b01a + 92a8d41 commit e29dc45

File tree

3 files changed

+115
-59
lines changed

3 files changed

+115
-59
lines changed

spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/TypeUtils.java

+18-35
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2024 the original author or authors.
2+
* Copyright 2012-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -23,7 +23,6 @@
2323
import java.util.HashMap;
2424
import java.util.List;
2525
import java.util.Map;
26-
import java.util.Map.Entry;
2726
import java.util.regex.Matcher;
2827
import java.util.regex.Pattern;
2928
import java.util.stream.Collectors;
@@ -47,6 +46,7 @@
4746
* @author Stephane Nicoll
4847
* @author Phillip Webb
4948
* @author Pavel Anisimov
49+
* @author Dmytro Nosan
5050
*/
5151
class TypeUtils {
5252

@@ -135,7 +135,7 @@ String getType(TypeElement element, TypeMirror type) {
135135
if (type == null) {
136136
return null;
137137
}
138-
return type.accept(this.typeExtractor, createTypeDescriptor(element));
138+
return type.accept(this.typeExtractor, resolveTypeDescriptor(element));
139139
}
140140

141141
/**
@@ -218,7 +218,7 @@ private TypeKind getPrimitiveFor(TypeMirror type) {
218218
return WRAPPER_TO_PRIMITIVE.get(type.toString());
219219
}
220220

221-
TypeDescriptor resolveTypeDescriptor(TypeElement element) {
221+
private TypeDescriptor resolveTypeDescriptor(TypeElement element) {
222222
if (this.typeDescriptors.containsKey(element)) {
223223
return this.typeDescriptors.get(element);
224224
}
@@ -319,22 +319,22 @@ private String determineQualifiedName(DeclaredType type, TypeElement enclosingEl
319319
}
320320

321321
@Override
322-
public String visitTypeVariable(TypeVariable t, TypeDescriptor descriptor) {
323-
TypeMirror typeMirror = descriptor.resolveGeneric(t);
324-
if (typeMirror != null) {
325-
if (typeMirror instanceof TypeVariable typeVariable) {
322+
public String visitTypeVariable(TypeVariable typeVariable, TypeDescriptor descriptor) {
323+
TypeMirror resolvedGeneric = descriptor.resolveGeneric(typeVariable);
324+
if (resolvedGeneric != null) {
325+
if (resolvedGeneric instanceof TypeVariable resolveTypeVariable) {
326326
// Still unresolved, let's use the upper bound, checking first if
327327
// a cycle may exist
328-
if (!hasCycle(typeVariable)) {
329-
return visit(typeVariable.getUpperBound(), descriptor);
328+
if (!hasCycle(resolveTypeVariable)) {
329+
return visit(resolveTypeVariable.getUpperBound(), descriptor);
330330
}
331331
}
332332
else {
333-
return visit(typeMirror, descriptor);
333+
return visit(resolvedGeneric, descriptor);
334334
}
335335
}
336336
// Fallback to simple representation of the upper bound
337-
return defaultAction(t.getUpperBound(), descriptor);
337+
return defaultAction(typeVariable.getUpperBound(), descriptor);
338338
}
339339

340340
private boolean hasCycle(TypeVariable variable) {
@@ -394,37 +394,20 @@ static class TypeDescriptor {
394394

395395
private final Map<TypeVariable, TypeMirror> generics = new HashMap<>();
396396

397-
Map<TypeVariable, TypeMirror> getGenerics() {
398-
return Collections.unmodifiableMap(this.generics);
399-
}
400-
401397
TypeMirror resolveGeneric(TypeVariable typeVariable) {
402-
return resolveGeneric(getParameterName(typeVariable));
403-
}
404-
405-
TypeMirror resolveGeneric(String parameterName) {
406-
return this.generics.entrySet()
407-
.stream()
408-
.filter((e) -> getParameterName(e.getKey()).equals(parameterName))
409-
.findFirst()
410-
.map(Entry::getValue)
411-
.orElse(null);
398+
TypeMirror resolved = this.generics.get(typeVariable);
399+
if (resolved != typeVariable && resolved instanceof TypeVariable resolvedTypeVariable) {
400+
return resolveGeneric(resolvedTypeVariable);
401+
}
402+
return resolved;
412403
}
413404

414405
private void registerIfNecessary(TypeMirror variable, TypeMirror resolution) {
415406
if (variable instanceof TypeVariable typeVariable) {
416-
if (this.generics.keySet()
417-
.stream()
418-
.noneMatch((candidate) -> getParameterName(candidate).equals(getParameterName(typeVariable)))) {
419-
this.generics.put(typeVariable, resolution);
420-
}
407+
this.generics.put(typeVariable, resolution);
421408
}
422409
}
423410

424-
private String getParameterName(TypeVariable typeVariable) {
425-
return typeVariable.asElement().getSimpleName().toString();
426-
}
427-
428411
}
429412

430413
}
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2023 the original author or authors.
2+
* Copyright 2012-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -17,16 +17,23 @@
1717
package org.springframework.boot.configurationprocessor;
1818

1919
import java.time.Duration;
20+
import java.util.Map;
2021
import java.util.function.BiConsumer;
2122

23+
import javax.lang.model.element.TypeElement;
24+
import javax.lang.model.element.VariableElement;
25+
import javax.lang.model.type.TypeMirror;
26+
import javax.lang.model.util.ElementFilter;
27+
2228
import org.junit.jupiter.api.Test;
2329

24-
import org.springframework.boot.configurationprocessor.TypeUtils.TypeDescriptor;
2530
import org.springframework.boot.configurationprocessor.test.RoundEnvironmentTester;
2631
import org.springframework.boot.configurationprocessor.test.TestableAnnotationProcessor;
2732
import org.springframework.boot.configurationsample.generic.AbstractGenericProperties;
2833
import org.springframework.boot.configurationsample.generic.AbstractIntermediateGenericProperties;
34+
import org.springframework.boot.configurationsample.generic.MixGenericNameProperties;
2935
import org.springframework.boot.configurationsample.generic.SimpleGenericProperties;
36+
import org.springframework.boot.configurationsample.generic.UnresolvedGenericProperties;
3037
import org.springframework.core.test.tools.SourceFile;
3138
import org.springframework.core.test.tools.TestCompiler;
3239

@@ -41,40 +48,52 @@
4148
class TypeUtilsTests {
4249

4350
@Test
44-
void resolveTypeDescriptorOnConcreteClass() {
51+
void resolveTypeOnConcreteClass() {
4552
process(SimpleGenericProperties.class, (roundEnv, typeUtils) -> {
46-
TypeDescriptor typeDescriptor = typeUtils
47-
.resolveTypeDescriptor(roundEnv.getRootElement(SimpleGenericProperties.class));
48-
assertThat(typeDescriptor.getGenerics().keySet().stream().map(Object::toString)).containsOnly("A", "B",
49-
"C");
50-
assertThat(typeDescriptor.resolveGeneric("A")).hasToString(String.class.getName());
51-
assertThat(typeDescriptor.resolveGeneric("B")).hasToString(Integer.class.getName());
52-
assertThat(typeDescriptor.resolveGeneric("C")).hasToString(Duration.class.getName());
53-
53+
TypeElement typeElement = roundEnv.getRootElement(SimpleGenericProperties.class);
54+
assertThat(getTypeOfField(typeUtils, typeElement, "name")).hasToString(String.class.getName());
55+
assertThat(getTypeOfField(typeUtils, typeElement, "mappings"))
56+
.hasToString(constructMapType(Integer.class, Duration.class));
5457
});
5558
}
5659

5760
@Test
58-
void resolveTypeDescriptorOnIntermediateClass() {
61+
void resolveTypeOnIntermediateClass() {
5962
process(AbstractIntermediateGenericProperties.class, (roundEnv, typeUtils) -> {
60-
TypeDescriptor typeDescriptor = typeUtils
61-
.resolveTypeDescriptor(roundEnv.getRootElement(AbstractIntermediateGenericProperties.class));
62-
assertThat(typeDescriptor.getGenerics().keySet().stream().map(Object::toString)).containsOnly("A", "B",
63-
"C");
64-
assertThat(typeDescriptor.resolveGeneric("A")).hasToString(String.class.getName());
65-
assertThat(typeDescriptor.resolveGeneric("B")).hasToString(Integer.class.getName());
66-
assertThat(typeDescriptor.resolveGeneric("C")).hasToString("C");
63+
TypeElement typeElement = roundEnv.getRootElement(AbstractIntermediateGenericProperties.class);
64+
assertThat(getTypeOfField(typeUtils, typeElement, "name")).hasToString(String.class.getName());
65+
assertThat(getTypeOfField(typeUtils, typeElement, "mappings"))
66+
.hasToString(constructMapType(Integer.class, Object.class));
6767
});
6868
}
6969

7070
@Test
71-
void resolveTypeDescriptorWithOnlyGenerics() {
71+
void resolveTypeWithOnlyGenerics() {
7272
process(AbstractGenericProperties.class, (roundEnv, typeUtils) -> {
73-
TypeDescriptor typeDescriptor = typeUtils
74-
.resolveTypeDescriptor(roundEnv.getRootElement(AbstractGenericProperties.class));
75-
assertThat(typeDescriptor.getGenerics().keySet().stream().map(Object::toString)).containsOnly("A", "B",
76-
"C");
73+
TypeElement typeElement = roundEnv.getRootElement(AbstractGenericProperties.class);
74+
assertThat(getTypeOfField(typeUtils, typeElement, "name")).hasToString(Object.class.getName());
75+
assertThat(getTypeOfField(typeUtils, typeElement, "mappings"))
76+
.hasToString(constructMapType(Object.class, Object.class));
77+
});
78+
}
7779

80+
@Test
81+
void resolveTypeWithUnresolvedGenericProperties() {
82+
process(UnresolvedGenericProperties.class, (roundEnv, typeUtils) -> {
83+
TypeElement typeElement = roundEnv.getRootElement(UnresolvedGenericProperties.class);
84+
assertThat(getTypeOfField(typeUtils, typeElement, "name")).hasToString(String.class.getName());
85+
assertThat(getTypeOfField(typeUtils, typeElement, "mappings"))
86+
.hasToString(constructMapType(Number.class, Object.class));
87+
});
88+
}
89+
90+
@Test
91+
void resolvedTypeMixGenericNamePropertiesProperties() {
92+
process(MixGenericNameProperties.class, (roundEnv, typeUtils) -> {
93+
TypeElement typeElement = roundEnv.getRootElement(MixGenericNameProperties.class);
94+
assertThat(getTypeOfField(typeUtils, typeElement, "name")).hasToString(String.class.getName());
95+
assertThat(getTypeOfField(typeUtils, typeElement, "mappings"))
96+
.hasToString(constructMapType(Number.class, Object.class));
7897
});
7998
}
8099

@@ -87,4 +106,29 @@ private void process(Class<?> target, BiConsumer<RoundEnvironmentTester, TypeUti
87106
});
88107
}
89108

109+
private String constructMapType(Class<?> keyType, Class<?> valueType) {
110+
return "%s<%s,%s>".formatted(Map.class.getName(), keyType.getName(), valueType.getName());
111+
}
112+
113+
private String getTypeOfField(TypeUtils typeUtils, TypeElement typeElement, String name) {
114+
TypeMirror field = findField(typeUtils, typeElement, name);
115+
if (field == null) {
116+
throw new IllegalStateException("Unable to find field '" + name + "' in " + typeElement);
117+
}
118+
return typeUtils.getType(typeElement, field);
119+
}
120+
121+
private TypeMirror findField(TypeUtils typeUtils, TypeElement typeElement, String name) {
122+
for (VariableElement variableElement : ElementFilter.fieldsIn(typeElement.getEnclosedElements())) {
123+
if (variableElement.getSimpleName().contentEquals(name)) {
124+
return variableElement.asType();
125+
}
126+
}
127+
TypeMirror superclass = typeElement.getSuperclass();
128+
if (superclass != null && !superclass.toString().equals(Object.class.getName())) {
129+
return findField(typeUtils, (TypeElement) typeUtils.asElement(superclass), name);
130+
}
131+
return null;
132+
}
133+
90134
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/*
2+
* Copyright 2012-2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.configurationsample.generic;
18+
19+
/**
20+
* Properties with unresolved generic types that use identical generic parameter names but
21+
* differ in their positions.
22+
*
23+
* @param <C> mapping name type
24+
* @param <B> mapping value type
25+
* @author Dmytro Nosan
26+
*/
27+
public class MixGenericNameProperties<B, C extends Number> extends AbstractGenericProperties<String, C, B> {
28+
29+
}

0 commit comments

Comments
 (0)