Skip to content

Commit 59ea7c1

Browse files
committed
Use most specific getter when generating metadata
This commit makes sure to use the most specific getter if more than one candidate exists. Closes gh-24002
1 parent 006d4bc commit 59ea7c1

File tree

5 files changed

+118
-26
lines changed

5 files changed

+118
-26
lines changed

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

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2019 the original author or authors.
2+
* Copyright 2012-2020 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.
@@ -91,10 +91,12 @@ Stream<PropertyDescriptor<?>> resolveJavaBeanProperties(TypeElement type, Execut
9191
TypeElementMembers members) {
9292
// First check if we have regular java bean properties there
9393
Map<String, PropertyDescriptor<?>> candidates = new LinkedHashMap<>();
94-
members.getPublicGetters().forEach((name, getter) -> {
94+
members.getPublicGetters().forEach((name, getters) -> {
95+
VariableElement field = members.getFields().get(name);
96+
ExecutableElement getter = findMatchingGetter(members, getters, field);
9597
TypeMirror propertyType = getter.getReturnType();
96-
register(candidates, new JavaBeanPropertyDescriptor(type, factoryMethod, getter, name, propertyType,
97-
members.getFields().get(name), members.getPublicSetter(name, propertyType)));
98+
register(candidates, new JavaBeanPropertyDescriptor(type, factoryMethod, getter, name, propertyType, field,
99+
members.getPublicSetter(name, propertyType)));
98100
});
99101
// Then check for Lombok ones
100102
members.getFields().forEach((name, field) -> {
@@ -107,6 +109,14 @@ Stream<PropertyDescriptor<?>> resolveJavaBeanProperties(TypeElement type, Execut
107109
return candidates.values().stream();
108110
}
109111

112+
private ExecutableElement findMatchingGetter(TypeElementMembers members, List<ExecutableElement> candidates,
113+
VariableElement field) {
114+
if (candidates.size() > 1 && field != null) {
115+
return members.getMatchingGetter(candidates, field.asType());
116+
}
117+
return candidates.get(0);
118+
}
119+
110120
private void register(Map<String, PropertyDescriptor<?>> candidates, PropertyDescriptor<?> descriptor) {
111121
if (!candidates.containsKey(descriptor.getName()) && isCandidate(descriptor)) {
112122
candidates.put(descriptor.getName(), descriptor);

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

Lines changed: 31 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import java.util.List;
2323
import java.util.Map;
2424
import java.util.Set;
25+
import java.util.function.Function;
2526

2627
import javax.lang.model.element.Element;
2728
import javax.lang.model.element.ExecutableElement;
@@ -48,7 +49,7 @@ class TypeElementMembers {
4849

4950
private final Map<String, VariableElement> fields = new LinkedHashMap<>();
5051

51-
private final Map<String, ExecutableElement> publicGetters = new LinkedHashMap<>();
52+
private final Map<String, List<ExecutableElement>> publicGetters = new LinkedHashMap<>();
5253

5354
private final Map<String, List<ExecutableElement>> publicSetters = new LinkedHashMap<>();
5455

@@ -74,8 +75,14 @@ private void process(TypeElement element) {
7475
private void processMethod(ExecutableElement method) {
7576
if (isPublic(method)) {
7677
String name = method.getSimpleName().toString();
77-
if (isGetter(method) && !this.publicGetters.containsKey(getAccessorName(name))) {
78-
this.publicGetters.put(getAccessorName(name), method);
78+
if (isGetter(method)) {
79+
String propertyName = getAccessorName(name);
80+
List<ExecutableElement> matchingGetters = this.publicGetters.computeIfAbsent(propertyName,
81+
(k) -> new ArrayList<>());
82+
TypeMirror returnType = method.getReturnType();
83+
if (getMatchingGetter(matchingGetters, returnType) == null) {
84+
matchingGetters.add(method);
85+
}
7986
}
8087
else if (isSetter(method)) {
8188
String propertyName = getAccessorName(name);
@@ -95,10 +102,19 @@ private boolean isPublic(ExecutableElement method) {
95102
&& !modifiers.contains(Modifier.STATIC);
96103
}
97104

105+
ExecutableElement getMatchingGetter(List<ExecutableElement> candidates, TypeMirror type) {
106+
return getMatchingAccessor(candidates, type, ExecutableElement::getReturnType);
107+
}
108+
98109
private ExecutableElement getMatchingSetter(List<ExecutableElement> candidates, TypeMirror type) {
110+
return getMatchingAccessor(candidates, type, (candidate) -> candidate.getParameters().get(0).asType());
111+
}
112+
113+
private ExecutableElement getMatchingAccessor(List<ExecutableElement> candidates, TypeMirror type,
114+
Function<ExecutableElement, TypeMirror> typeExtractor) {
99115
for (ExecutableElement candidate : candidates) {
100-
TypeMirror paramType = candidate.getParameters().get(0).asType();
101-
if (this.env.getTypeUtils().isSameType(paramType, type)) {
116+
TypeMirror candidateType = typeExtractor.apply(candidate);
117+
if (this.env.getTypeUtils().isSameType(candidateType, type)) {
102118
return candidate;
103119
}
104120
}
@@ -151,35 +167,30 @@ Map<String, VariableElement> getFields() {
151167
return Collections.unmodifiableMap(this.fields);
152168
}
153169

154-
Map<String, ExecutableElement> getPublicGetters() {
170+
Map<String, List<ExecutableElement>> getPublicGetters() {
155171
return Collections.unmodifiableMap(this.publicGetters);
156172
}
157173

158174
ExecutableElement getPublicGetter(String name, TypeMirror type) {
159-
ExecutableElement candidate = this.publicGetters.get(name);
160-
if (candidate != null) {
161-
TypeMirror returnType = candidate.getReturnType();
162-
if (this.env.getTypeUtils().isSameType(returnType, type)) {
163-
return candidate;
164-
}
165-
TypeMirror alternative = this.env.getTypeUtils().getWrapperOrPrimitiveFor(type);
166-
if (alternative != null && this.env.getTypeUtils().isSameType(returnType, alternative)) {
167-
return candidate;
168-
}
169-
}
170-
return null;
175+
List<ExecutableElement> candidates = this.publicGetters.get(name);
176+
return getPublicAccessor(candidates, type, (specificType) -> getMatchingGetter(candidates, specificType));
171177
}
172178

173179
ExecutableElement getPublicSetter(String name, TypeMirror type) {
174180
List<ExecutableElement> candidates = this.publicSetters.get(name);
181+
return getPublicAccessor(candidates, type, (specificType) -> getMatchingSetter(candidates, specificType));
182+
}
183+
184+
private ExecutableElement getPublicAccessor(List<ExecutableElement> candidates, TypeMirror type,
185+
Function<TypeMirror, ExecutableElement> matchingAccessorExtractor) {
175186
if (candidates != null) {
176-
ExecutableElement matching = getMatchingSetter(candidates, type);
187+
ExecutableElement matching = matchingAccessorExtractor.apply(type);
177188
if (matching != null) {
178189
return matching;
179190
}
180191
TypeMirror alternative = this.env.getTypeUtils().getWrapperOrPrimitiveFor(type);
181192
if (alternative != null) {
182-
return getMatchingSetter(candidates, alternative);
193+
return matchingAccessorExtractor.apply(alternative);
183194
}
184195
}
185196
return null;

spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessorTests.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import org.springframework.boot.configurationsample.specific.AnnotatedGetter;
3939
import org.springframework.boot.configurationsample.specific.BoxingPojo;
4040
import org.springframework.boot.configurationsample.specific.BuilderPojo;
41+
import org.springframework.boot.configurationsample.specific.DeprecatedLessPreciseTypePojo;
4142
import org.springframework.boot.configurationsample.specific.DeprecatedUnrelatedMethodPojo;
4243
import org.springframework.boot.configurationsample.specific.DoubleRegistrationProperties;
4344
import org.springframework.boot.configurationsample.specific.EmptyDefaultValueProperties;
@@ -192,12 +193,22 @@ void deprecatedOnUnrelatedSetter() {
192193
}
193194

194195
@Test
195-
void boxingOnSetter() {
196+
void deprecatedWithLessPreciseType() {
197+
Class<?> type = DeprecatedLessPreciseTypePojo.class;
198+
ConfigurationMetadata metadata = compile(type);
199+
assertThat(metadata).has(Metadata.withGroup("not.deprecated").fromSource(type));
200+
assertThat(metadata).has(Metadata.withProperty("not.deprecated.flag", Boolean.class).withDefaultValue(false)
201+
.withNoDeprecation().fromSource(type));
202+
}
203+
204+
@Test
205+
void typBoxing() {
196206
Class<?> type = BoxingPojo.class;
197207
ConfigurationMetadata metadata = compile(type);
198208
assertThat(metadata).has(Metadata.withGroup("boxing").fromSource(type));
199209
assertThat(metadata)
200210
.has(Metadata.withProperty("boxing.flag", Boolean.class).withDefaultValue(false).fromSource(type));
211+
assertThat(metadata).has(Metadata.withProperty("boxing.another-flag", Boolean.class).fromSource(type));
201212
assertThat(metadata).has(Metadata.withProperty("boxing.counter", Integer.class).fromSource(type));
202213
}
203214

spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/specific/BoxingPojo.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2019 the original author or authors.
2+
* Copyright 2012-2020 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.
@@ -29,6 +29,8 @@ public class BoxingPojo {
2929

3030
private boolean flag;
3131

32+
private Boolean anotherFlag;
33+
3234
private Integer counter;
3335

3436
public boolean isFlag() {
@@ -40,6 +42,14 @@ public void setFlag(Boolean flag) {
4042
this.flag = flag;
4143
}
4244

45+
public boolean isAnotherFlag() {
46+
return Boolean.TRUE.equals(this.anotherFlag);
47+
}
48+
49+
public void setAnotherFlag(boolean anotherFlag) {
50+
this.anotherFlag = anotherFlag;
51+
}
52+
4353
public Integer getCounter() {
4454
return this.counter;
4555
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
* Copyright 2012-2020 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.specific;
18+
19+
import org.springframework.boot.configurationsample.ConfigurationProperties;
20+
21+
/**
22+
* Demonstrate that deprecating accessor with not the same type is not taken into account
23+
* to detect the deprecated flag.
24+
*
25+
* @author Stephane Nicoll
26+
*/
27+
@ConfigurationProperties("not.deprecated")
28+
public class DeprecatedLessPreciseTypePojo {
29+
30+
private boolean flag;
31+
32+
@Deprecated
33+
public Boolean getFlag() {
34+
return this.flag;
35+
}
36+
37+
public boolean isFlag() {
38+
return this.flag;
39+
}
40+
41+
public void setFlag(boolean flag) {
42+
this.flag = flag;
43+
}
44+
45+
@Deprecated
46+
public void setFlag(Boolean flag) {
47+
this.flag = flag;
48+
}
49+
50+
}

0 commit comments

Comments
 (0)