Support of Lombok annotated ConfigurationProperties

Previously, no configuration properties were discovered on a class using
lombok instead of regular getters/setters.

This commit adds a support for some of the lombok annotations,
specifically that is @Data, @Getter and @Setter. Provides the same
semantic as what lombok is generating.

Closes gh-2114
This commit is contained in:
Stephane Nicoll
2014-12-11 17:51:26 +01:00
parent 82c6142166
commit 77427f53cc
8 changed files with 286 additions and 12 deletions

View File

@@ -40,6 +40,7 @@ import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Elements;
import javax.tools.Diagnostic.Kind;
import javax.tools.FileObject;
@@ -69,6 +70,12 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor
static final String NESTED_CONFIGURATION_PROPERTY_ANNOTATION = "org.springframework.boot."
+ "context.properties.NestedConfigurationProperty";
static final String LOMBOK_DATA_ANNOTATION = "lombok.Data";
static final String LOMBOK_GETTER_ANNOTATION = "lombok.Getter";
static final String LOMBOK_SETTER_ANNOTATION = "lombok.Setter";
private ConfigurationMetadata metadata;
private TypeUtils typeUtils;
@@ -157,6 +164,7 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor
TypeElementMembers members = new TypeElementMembers(this.processingEnv, element);
Map<String, Object> fieldValues = getFieldValues(element);
processSimpleTypes(prefix, element, members, fieldValues);
processLombokTypes(prefix, element, members, fieldValues);
processNestedTypes(prefix, element, members);
}
@@ -177,15 +185,14 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor
ExecutableElement getter = entry.getValue();
ExecutableElement setter = members.getPublicSetters().get(name);
VariableElement field = members.getFields().get(name);
Element returnType = this.processingEnv.getTypeUtils().asElement(
getter.getReturnType());
boolean isExcluded = this.typeExcludeFilter.isExcluded(getter
.getReturnType());
boolean isNested = isNested(returnType, field, element);
boolean isCollection = this.typeUtils.isCollectionOrMap(getter
.getReturnType());
TypeMirror returnType = getter.getReturnType();
Element returnTypeElement = this.processingEnv.getTypeUtils().asElement(
returnType);
boolean isExcluded = this.typeExcludeFilter.isExcluded(returnType);
boolean isNested = isNested(returnTypeElement, field, element);
boolean isCollection = this.typeUtils.isCollectionOrMap(returnType);
if (!isExcluded && !isNested && (setter != null || isCollection)) {
String dataType = this.typeUtils.getType(getter.getReturnType());
String dataType = this.typeUtils.getType(returnType);
String sourceType = this.typeUtils.getType(element);
String description = this.typeUtils.getJavaDoc(field);
Object defaultValue = fieldValues.get(name);
@@ -198,6 +205,48 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor
}
}
private void processLombokTypes(String prefix, TypeElement element,
TypeElementMembers members, Map<String, Object> fieldValues) {
for (Map.Entry<String, VariableElement> entry : members.getFields()
.entrySet()) {
String name = entry.getKey();
VariableElement field = entry.getValue();
if (!isLombokField(field, element)) {
continue;
}
TypeMirror returnType = field.asType();
Element returnTypeElement = this.processingEnv.getTypeUtils().asElement(
returnType);
boolean isExcluded = this.typeExcludeFilter.isExcluded(returnType);
boolean isNested = isNested(returnTypeElement, field, element);
boolean isCollection = this.typeUtils.isCollectionOrMap(returnType);
boolean hasSetter = hasLombokSetter(field, element);
if (!isExcluded && !isNested && (hasSetter || isCollection)) {
String dataType = this.typeUtils.getType(returnType);
String sourceType = this.typeUtils.getType(element);
String description = this.typeUtils.getJavaDoc(field);
Object defaultValue = fieldValues.get(name);
boolean deprecated = hasDeprecateAnnotation(field)
|| hasDeprecateAnnotation(element);
this.metadata.add(ItemMetadata.newProperty(prefix, name, dataType,
sourceType, null, description, defaultValue, deprecated));
}
}
}
private boolean isLombokField(VariableElement field, TypeElement element) {
return hasAnnotation(field, LOMBOK_GETTER_ANNOTATION)
|| hasAnnotation(element, LOMBOK_GETTER_ANNOTATION)
|| hasAnnotation(element, LOMBOK_DATA_ANNOTATION);
}
private boolean hasLombokSetter(VariableElement field, TypeElement element) {
return !field.getModifiers().contains(Modifier.FINAL) && (
hasAnnotation(field, LOMBOK_SETTER_ANNOTATION)
|| hasAnnotation(element, LOMBOK_SETTER_ANNOTATION)
|| hasAnnotation(element, LOMBOK_DATA_ANNOTATION));
}
private void processNestedTypes(String prefix, TypeElement element,
TypeElementMembers members) {
for (Map.Entry<String, ExecutableElement> entry : members.getPublicGetters()
@@ -223,7 +272,7 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor
private boolean isNested(Element returnType, VariableElement field,
TypeElement element) {
if (getAnnotation(field, nestedConfigurationPropertyAnnotation()) != null) {
if (hasAnnotation(field, nestedConfigurationPropertyAnnotation())) {
return true;
}
return this.typeUtils.isEnclosedIn(returnType, element)
@@ -231,7 +280,11 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor
}
private boolean hasDeprecateAnnotation(Element element) {
return getAnnotation(element, "java.lang.Deprecated") != null;
return hasAnnotation(element, "java.lang.Deprecated");
}
private boolean hasAnnotation(Element element, String type) {
return getAnnotation(element, type) != null;
}
private AnnotationMirror getAnnotation(Element element, String type) {

View File

@@ -17,7 +17,6 @@
package org.springframework.boot.configurationprocessor;
import java.io.IOException;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
@@ -25,7 +24,11 @@ import javax.lang.model.SourceVersion;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.springframework.boot.configurationprocessor.metadata.ConfigurationMetadata;
import org.springframework.boot.configurationsample.lombok.LombokSimpleDataProperties;
import org.springframework.boot.configurationsample.lombok.LombokExplicitProperties;
import org.springframework.boot.configurationsample.lombok.LombokSimpleProperties;
import org.springframework.boot.configurationsample.method.EmptyTypeMethodConfig;
import org.springframework.boot.configurationsample.method.InvalidMethodConfig;
import org.springframework.boot.configurationsample.method.MethodAndClassConfig;
@@ -286,13 +289,49 @@ public class ConfigurationMetadataAnnotationProcessorTests {
assertThat(metadata, not(containsProperty("excluded.writer-array")));
}
@Test
public void lombokDataProperties() throws Exception {
ConfigurationMetadata metadata = compile(LombokSimpleDataProperties.class);
assertSimpleLombokProperties(metadata, LombokSimpleDataProperties.class, "data");
}
@Test
public void lombokSimpleProperties() throws Exception {
ConfigurationMetadata metadata = compile(LombokSimpleProperties.class);
assertSimpleLombokProperties(metadata, LombokSimpleProperties.class, "simple");
}
@Test
public void lombokExplicitProperties() throws Exception {
ConfigurationMetadata metadata = compile(LombokExplicitProperties.class);
assertSimpleLombokProperties(metadata, LombokExplicitProperties.class, "explicit");
}
private void assertSimpleLombokProperties(ConfigurationMetadata metadata, Class<?> source, String prefix) {
assertThat(metadata, containsGroup(prefix).fromSource(source));
assertThat(metadata, not(containsProperty(prefix + ".id")));
assertThat(
metadata,
containsProperty(prefix + ".name", String.class)
.fromSource(source)
.withDescription("Name description."));
assertThat(metadata, containsProperty(prefix + ".description"));
assertThat(metadata, containsProperty(prefix + ".counter"));
assertThat(metadata,
containsProperty(prefix + ".number").fromSource(source)
.withDefaultValue(is(0))
.withDeprecated());
assertThat(metadata, containsProperty(prefix + ".items"));
assertThat(metadata, not(containsProperty(prefix + ".ignored")));
}
private ConfigurationMetadata compile(Class<?>... types) throws IOException {
TestConfigurationMetadataAnnotationProcessor processor = new TestConfigurationMetadataAnnotationProcessor();
new TestCompiler(this.temporaryFolder).getTask(types).call(processor);
return processor.getMetadata();
}
@SupportedAnnotationTypes({ "*" })
@SupportedAnnotationTypes({"*"})
@SupportedSourceVersion(SourceVersion.RELEASE_6)
private static class TestConfigurationMetadataAnnotationProcessor extends
ConfigurationMetadataAnnotationProcessor {

View File

@@ -0,0 +1,59 @@
/*
* Copyright 2012-2014 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.configurationsample.lombok;
import java.util.ArrayList;
import java.util.List;
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.configurationsample.ConfigurationProperties;
/**
* Configuration properties using lombok @Getter/@Setter at field level.
*
* @author Stephane Nicoll
*/
@ConfigurationProperties(prefix = "explicit")
public class LombokExplicitProperties {
@Getter
private final String id = "super-id";
/**
* Name description.
*/
@Getter @Setter
private String name;
@Getter @Setter
private String description;
@Getter @Setter
private Integer counter;
@Deprecated @Getter @Setter
private Integer number = 0;
@Getter
private final List<String> items = new ArrayList<String>();
// Should be ignored if no annotation is set
private String ignored;
}

View File

@@ -0,0 +1,53 @@
/*
* Copyright 2012-2014 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.configurationsample.lombok;
import java.util.ArrayList;
import java.util.List;
import lombok.Data;
import org.springframework.boot.configurationsample.ConfigurationProperties;
/**
* Configuration properties using lombok @Data.
*
* @author Stephane Nicoll
*/
@Data
@ConfigurationProperties(prefix = "data")
public class LombokSimpleDataProperties {
private final String id = "super-id";
/**
* Name description.
*/
private String name;
private String description;
private Integer counter;
@Deprecated
private Integer number = 0;
private final List<String> items = new ArrayList<String>();
private final String ignored = "foo";
}

View File

@@ -0,0 +1,55 @@
/*
* Copyright 2012-2014 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.configurationsample.lombok;
import java.util.ArrayList;
import java.util.List;
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.configurationsample.ConfigurationProperties;
/**
* Configuration properties using lombok @Getter/@Setter at class level.
*
* @author Stephane Nicoll
*/
@Getter
@Setter
@ConfigurationProperties(prefix = "simple")
public class LombokSimpleProperties {
private final String id = "super-id";
/**
* Name description.
*/
private String name;
private String description;
private Integer counter;
@Deprecated
private Integer number = 0;
private final List<String> items = new ArrayList<String>();
private final String ignored = "foo";
}