Skip to content

Commit 586507c

Browse files
committed
Generate auto-configuration OnBean data
Update the auto-configuration annotation processor to generate properties for `@ConditionalOnBean` and `@ConditionalOnSingleCandidate`. See spring-projectsgh-13328
1 parent e4f54a4 commit 586507c

File tree

8 files changed

+303
-61
lines changed

8 files changed

+303
-61
lines changed

Diff for: spring-boot-project/spring-boot-tools/spring-boot-autoconfigure-processor/src/main/java/org/springframework/boot/autoconfigureprocessor/AutoConfigureAnnotationProcessor.java

+93-51
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,15 @@
1818

1919
import java.io.IOException;
2020
import java.io.OutputStream;
21+
import java.util.ArrayList;
22+
import java.util.Arrays;
2123
import java.util.Collections;
24+
import java.util.HashSet;
2225
import java.util.LinkedHashMap;
2326
import java.util.List;
2427
import java.util.Map;
25-
import java.util.Map.Entry;
2628
import java.util.Properties;
2729
import java.util.Set;
28-
import java.util.stream.Collectors;
2930
import java.util.stream.Stream;
3031

3132
import javax.annotation.processing.AbstractProcessor;
@@ -36,10 +37,8 @@
3637
import javax.lang.model.element.AnnotationValue;
3738
import javax.lang.model.element.Element;
3839
import javax.lang.model.element.ElementKind;
39-
import javax.lang.model.element.ExecutableElement;
4040
import javax.lang.model.element.TypeElement;
4141
import javax.lang.model.type.DeclaredType;
42-
import javax.lang.model.type.TypeMirror;
4342
import javax.tools.FileObject;
4443
import javax.tools.StandardLocation;
4544

@@ -52,6 +51,8 @@
5251
*/
5352
@SupportedAnnotationTypes({ "org.springframework.context.annotation.Configuration",
5453
"org.springframework.boot.autoconfigure.condition.ConditionalOnClass",
54+
"org.springframework.boot.autoconfigure.condition.ConditionalOnBean",
55+
"org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate",
5556
"org.springframework.boot.autoconfigure.AutoConfigureBefore",
5657
"org.springframework.boot.autoconfigure.AutoConfigureAfter",
5758
"org.springframework.boot.autoconfigure.AutoConfigureOrder" })
@@ -60,21 +61,30 @@ public class AutoConfigureAnnotationProcessor extends AbstractProcessor {
6061
protected static final String PROPERTIES_PATH = "META-INF/"
6162
+ "spring-autoconfigure-metadata.properties";
6263

63-
private Map<String, String> annotations;
64+
private final Map<String, String> annotations;
65+
66+
private final Map<String, ValueExtractor> valueExtractors;
6467

6568
private final Properties properties = new Properties();
6669

6770
public AutoConfigureAnnotationProcessor() {
6871
Map<String, String> annotations = new LinkedHashMap<>();
6972
addAnnotations(annotations);
7073
this.annotations = Collections.unmodifiableMap(annotations);
74+
Map<String, ValueExtractor> valueExtractors = new LinkedHashMap<>();
75+
addValueExtractors(valueExtractors);
76+
this.valueExtractors = Collections.unmodifiableMap(valueExtractors);
7177
}
7278

7379
protected void addAnnotations(Map<String, String> annotations) {
7480
annotations.put("Configuration",
7581
"org.springframework.context.annotation.Configuration");
7682
annotations.put("ConditionalOnClass",
7783
"org.springframework.boot.autoconfigure.condition.ConditionalOnClass");
84+
annotations.put("ConditionalOnBean",
85+
"org.springframework.boot.autoconfigure.condition.ConditionalOnBean");
86+
annotations.put("ConditionalOnSingleCandidate",
87+
"org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate");
7888
annotations.put("AutoConfigureBefore",
7989
"org.springframework.boot.autoconfigure.AutoConfigureBefore");
8090
annotations.put("AutoConfigureAfter",
@@ -83,6 +93,17 @@ protected void addAnnotations(Map<String, String> annotations) {
8393
"org.springframework.boot.autoconfigure.AutoConfigureOrder");
8494
}
8595

96+
private void addValueExtractors(Map<String, ValueExtractor> attributes) {
97+
attributes.put("Configuration", ValueExtractor.allFrom("value"));
98+
attributes.put("ConditionalOnClass", ValueExtractor.allFrom("value", "name"));
99+
attributes.put("ConditionalOnBean", new OnBeanConditionValueExtractor());
100+
attributes.put("ConditionalOnSingleCandidate",
101+
new OnBeanConditionValueExtractor());
102+
attributes.put("AutoConfigureBefore", ValueExtractor.allFrom("value", "name"));
103+
attributes.put("AutoConfigureAfter", ValueExtractor.allFrom("value", "name"));
104+
attributes.put("AutoConfigureOrder", ValueExtractor.allFrom("value"));
105+
}
106+
86107
@Override
87108
public SourceVersion getSupportedSourceVersion() {
88109
return SourceVersion.latestSupported();
@@ -123,10 +144,10 @@ private void process(RoundEnvironment roundEnv, String propertyKey,
123144
private void processElement(Element element, String propertyKey,
124145
String annotationName) {
125146
try {
126-
String qualifiedName = getQualifiedName(element);
147+
String qualifiedName = Elements.getQualifiedName(element);
127148
AnnotationMirror annotation = getAnnotation(element, annotationName);
128149
if (qualifiedName != null && annotation != null) {
129-
List<Object> values = getValues(annotation);
150+
List<Object> values = getValues(propertyKey, annotation);
130151
this.properties.put(qualifiedName + "." + propertyKey,
131152
toCommaDelimitedString(values));
132153
this.properties.put(qualifiedName, "");
@@ -158,68 +179,89 @@ private String toCommaDelimitedString(List<Object> list) {
158179
return result.toString();
159180
}
160181

161-
private List<Object> getValues(AnnotationMirror annotation) {
162-
return annotation.getElementValues().entrySet().stream()
163-
.filter(this::isNameOrValueAttribute).flatMap(this::getValues)
164-
.collect(Collectors.toList());
165-
}
166-
167-
private boolean isNameOrValueAttribute(Entry<? extends ExecutableElement, ?> entry) {
168-
String attributeName = entry.getKey().getSimpleName().toString();
169-
return "name".equals(attributeName) || "value".equals(attributeName);
182+
private List<Object> getValues(String propertyKey, AnnotationMirror annotation) {
183+
ValueExtractor extractor = this.valueExtractors.get(propertyKey);
184+
if (extractor == null) {
185+
return Collections.emptyList();
186+
}
187+
return extractor.getValues(annotation);
170188
}
171189

172-
@SuppressWarnings("unchecked")
173-
private Stream<Object> getValues(Entry<?, ? extends AnnotationValue> entry) {
174-
Object value = entry.getValue().getValue();
175-
if (value instanceof List) {
176-
return ((List<AnnotationValue>) value).stream()
177-
.map((annotation) -> processValue(annotation.getValue()));
190+
private void writeProperties() throws IOException {
191+
if (!this.properties.isEmpty()) {
192+
FileObject file = this.processingEnv.getFiler()
193+
.createResource(StandardLocation.CLASS_OUTPUT, "", PROPERTIES_PATH);
194+
try (OutputStream outputStream = file.openOutputStream()) {
195+
this.properties.store(outputStream, null);
196+
}
178197
}
179-
return Stream.of(processValue(value));
180198
}
181199

182-
private Object processValue(Object value) {
183-
if (value instanceof DeclaredType) {
184-
return getQualifiedName(((DeclaredType) value).asElement());
200+
@FunctionalInterface
201+
private interface ValueExtractor {
202+
203+
List<Object> getValues(AnnotationMirror annotation);
204+
205+
static ValueExtractor allFrom(String... attributes) {
206+
Set<String> names = new HashSet<>(Arrays.asList(attributes));
207+
return new AbstractValueExtractor() {
208+
209+
@Override
210+
public List<Object> getValues(AnnotationMirror annotation) {
211+
List<Object> result = new ArrayList<>();
212+
annotation.getElementValues().forEach((key, value) -> {
213+
if (names.contains(key.getSimpleName().toString())) {
214+
extractValues(value).forEach(result::add);
215+
}
216+
});
217+
return result;
218+
}
219+
220+
};
185221
}
186-
return value;
222+
187223
}
188224

189-
private String getQualifiedName(Element element) {
190-
if (element != null) {
191-
TypeElement enclosingElement = getEnclosingTypeElement(element.asType());
192-
if (enclosingElement != null) {
193-
return getQualifiedName(enclosingElement) + "$"
194-
+ ((DeclaredType) element.asType()).asElement().getSimpleName()
195-
.toString();
225+
private abstract static class AbstractValueExtractor implements ValueExtractor {
226+
227+
@SuppressWarnings("unchecked")
228+
protected Stream<Object> extractValues(AnnotationValue annotationValue) {
229+
if (annotationValue == null) {
230+
return Stream.empty();
196231
}
197-
if (element instanceof TypeElement) {
198-
return ((TypeElement) element).getQualifiedName().toString();
232+
Object value = annotationValue.getValue();
233+
if (value instanceof List) {
234+
return ((List<AnnotationValue>) value).stream()
235+
.map((annotation) -> extractValue(annotation.getValue()));
199236
}
237+
return Stream.of(extractValue(value));
200238
}
201-
return null;
202-
}
203239

204-
private TypeElement getEnclosingTypeElement(TypeMirror type) {
205-
if (type instanceof DeclaredType) {
206-
DeclaredType declaredType = (DeclaredType) type;
207-
Element enclosingElement = declaredType.asElement().getEnclosingElement();
208-
if (enclosingElement != null && enclosingElement instanceof TypeElement) {
209-
return (TypeElement) enclosingElement;
240+
private Object extractValue(Object value) {
241+
if (value instanceof DeclaredType) {
242+
return Elements.getQualifiedName(((DeclaredType) value).asElement());
210243
}
244+
return value;
211245
}
212-
return null;
246+
213247
}
214248

215-
private void writeProperties() throws IOException {
216-
if (!this.properties.isEmpty()) {
217-
FileObject file = this.processingEnv.getFiler()
218-
.createResource(StandardLocation.CLASS_OUTPUT, "", PROPERTIES_PATH);
219-
try (OutputStream outputStream = file.openOutputStream()) {
220-
this.properties.store(outputStream, null);
249+
private static class OnBeanConditionValueExtractor extends AbstractValueExtractor {
250+
251+
@Override
252+
public List<Object> getValues(AnnotationMirror annotation) {
253+
Map<String, AnnotationValue> attributes = new LinkedHashMap<>();
254+
annotation.getElementValues().forEach((key, value) -> attributes
255+
.put(key.getSimpleName().toString(), value));
256+
if (attributes.containsKey("name")) {
257+
return Collections.emptyList();
221258
}
259+
List<Object> result = new ArrayList<>();
260+
extractValues(attributes.get("value")).forEach(result::add);
261+
extractValues(attributes.get("type")).forEach(result::add);
262+
return result;
222263
}
264+
223265
}
224266

225267
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/*
2+
* Copyright 2012-2018 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+
* http://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.autoconfigureprocessor;
18+
19+
import javax.lang.model.element.Element;
20+
import javax.lang.model.element.TypeElement;
21+
import javax.lang.model.type.DeclaredType;
22+
import javax.lang.model.type.TypeMirror;
23+
24+
/**
25+
* Utilities for dealing with {@link Element} classes.
26+
*
27+
* @author Phillip Webb
28+
*/
29+
final class Elements {
30+
31+
private Elements() {
32+
}
33+
34+
static String getQualifiedName(Element element) {
35+
if (element != null) {
36+
TypeElement enclosingElement = getEnclosingTypeElement(element.asType());
37+
if (enclosingElement != null) {
38+
return getQualifiedName(enclosingElement) + "$"
39+
+ ((DeclaredType) element.asType()).asElement().getSimpleName()
40+
.toString();
41+
}
42+
if (element instanceof TypeElement) {
43+
return ((TypeElement) element).getQualifiedName().toString();
44+
}
45+
}
46+
return null;
47+
}
48+
49+
private static TypeElement getEnclosingTypeElement(TypeMirror type) {
50+
if (type instanceof DeclaredType) {
51+
DeclaredType declaredType = (DeclaredType) type;
52+
Element enclosingElement = declaredType.asElement().getEnclosingElement();
53+
if (enclosingElement != null && enclosingElement instanceof TypeElement) {
54+
return (TypeElement) enclosingElement;
55+
}
56+
}
57+
return null;
58+
}
59+
60+
}

Diff for: spring-boot-project/spring-boot-tools/spring-boot-autoconfigure-processor/src/test/java/org/springframework/boot/autoconfigureprocessor/AutoConfigureAnnotationProcessorTests.java

+28-8
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2017 the original author or authors.
2+
* Copyright 2012-2018 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.
@@ -50,18 +50,38 @@ public void createCompiler() throws IOException {
5050
@Test
5151
public void annotatedClass() throws Exception {
5252
Properties properties = compile(TestClassConfiguration.class);
53-
assertThat(properties).hasSize(3);
53+
assertThat(properties).hasSize(5);
5454
assertThat(properties).containsEntry(
5555
"org.springframework.boot.autoconfigureprocessor."
5656
+ "TestClassConfiguration.ConditionalOnClass",
5757
"java.io.InputStream,org.springframework.boot.autoconfigureprocessor."
5858
+ "TestClassConfiguration$Nested");
59-
assertThat(properties).containsKey(
60-
"org.springframework.boot.autoconfigureprocessor.TestClassConfiguration");
61-
assertThat(properties).containsKey(
62-
"org.springframework.boot.autoconfigureprocessor.TestClassConfiguration.Configuration");
63-
assertThat(properties).doesNotContainKey(
64-
"org.springframework.boot.autoconfigureprocessor.TestClassConfiguration$Nested");
59+
assertThat(properties)
60+
.containsKey("org.springframework.boot.autoconfigureprocessor."
61+
+ "TestClassConfiguration");
62+
assertThat(properties)
63+
.containsKey("org.springframework.boot.autoconfigureprocessor."
64+
+ "TestClassConfiguration.Configuration");
65+
assertThat(properties)
66+
.doesNotContainKey("org.springframework.boot.autoconfigureprocessor."
67+
+ "TestClassConfiguration$Nested");
68+
assertThat(properties).containsEntry(
69+
"org.springframework.boot.autoconfigureprocessor."
70+
+ "TestClassConfiguration.ConditionalOnBean",
71+
"java.io.OutputStream");
72+
assertThat(properties).containsEntry(
73+
"org.springframework.boot.autoconfigureprocessor."
74+
+ "TestClassConfiguration.ConditionalOnSingleCandidate",
75+
"java.io.OutputStream");
76+
}
77+
78+
@Test
79+
public void annoatedClassWithOnBeanThatHasName() throws Exception {
80+
Properties properties = compile(TestOnBeanWithNameClassConfiguration.class);
81+
assertThat(properties).hasSize(3);
82+
assertThat(properties).containsEntry(
83+
"org.springframework.boot.autoconfigureprocessor.TestOnBeanWithNameClassConfiguration.ConditionalOnBean",
84+
"");
6585
}
6686

6787
@Test

Diff for: spring-boot-project/spring-boot-tools/spring-boot-autoconfigure-processor/src/test/java/org/springframework/boot/autoconfigureprocessor/TestClassConfiguration.java

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2017 the original author or authors.
2+
* Copyright 2012-2018 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,6 +23,8 @@
2323
*/
2424
@TestConfiguration
2525
@TestConditionalOnClass(name = "java.io.InputStream", value = TestClassConfiguration.Nested.class)
26+
@TestConditionalOnBean(type = "java.io.OutputStream")
27+
@TestConditionalOnSingleCandidate(type = "java.io.OutputStream")
2628
public class TestClassConfiguration {
2729

2830
@TestAutoConfigureOrder

Diff for: spring-boot-project/spring-boot-tools/spring-boot-autoconfigure-processor/src/test/java/org/springframework/boot/autoconfigureprocessor/TestConditionMetadataAnnotationProcessor.java

+6-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2017 the original author or authors.
2+
* Copyright 2012-2018 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.
@@ -32,6 +32,8 @@
3232
@SupportedAnnotationTypes({
3333
"org.springframework.boot.autoconfigureprocessor.TestConfiguration",
3434
"org.springframework.boot.autoconfigureprocessor.TestConditionalOnClass",
35+
"org.springframework.boot.autoconfigure.condition.TestConditionalOnBean",
36+
"org.springframework.boot.autoconfigure.condition.TestConditionalOnSingleCandidate",
3537
"org.springframework.boot.autoconfigureprocessor.TestAutoConfigureBefore",
3638
"org.springframework.boot.autoconfigureprocessor.TestAutoConfigureAfter",
3739
"org.springframework.boot.autoconfigureprocessor.TestAutoConfigureOrder" })
@@ -48,6 +50,9 @@ public TestConditionMetadataAnnotationProcessor(File outputLocation) {
4850
protected void addAnnotations(Map<String, String> annotations) {
4951
put(annotations, "Configuration", TestConfiguration.class);
5052
put(annotations, "ConditionalOnClass", TestConditionalOnClass.class);
53+
put(annotations, "ConditionalOnBean", TestConditionalOnBean.class);
54+
put(annotations, "ConditionalOnSingleCandidate",
55+
TestConditionalOnSingleCandidate.class);
5156
put(annotations, "AutoConfigureBefore", TestAutoConfigureBefore.class);
5257
put(annotations, "AutoConfigureAfter", TestAutoConfigureAfter.class);
5358
put(annotations, "AutoConfigureOrder", TestAutoConfigureOrder.class);

0 commit comments

Comments
 (0)