diff --git a/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessor.java b/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessor.java index 0111c3d1696..69afda4abb3 100644 --- a/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessor.java +++ b/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessor.java @@ -294,7 +294,8 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor String name = entry.getKey(); VariableElement field = entry.getValue(); if (isLombokField(field, element)) { - processNestedType(prefix, element, source, name, null, field, + ExecutableElement getter = members.getPublicGetter(name, field.asType()); + processNestedType(prefix, element, source, name, getter, field, field.asType()); } } diff --git a/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/MetadataCollector.java b/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/MetadataCollector.java index a478cf849ec..3757a27e225 100644 --- a/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/MetadataCollector.java +++ b/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/MetadataCollector.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2016 the original author or authors. + * Copyright 2012-2017 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. @@ -16,8 +16,8 @@ package org.springframework.boot.configurationprocessor; -import java.util.ArrayList; import java.util.HashSet; +import java.util.LinkedHashSet; import java.util.List; import java.util.Set; @@ -39,7 +39,7 @@ import org.springframework.boot.configurationprocessor.metadata.ItemMetadata; */ public class MetadataCollector { - private final List metadataItems = new ArrayList(); + private final Set metadataItems = new LinkedHashSet(); private final ProcessingEnvironment processingEnvironment; diff --git a/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/TypeElementMembers.java b/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/TypeElementMembers.java index 1c735eebc57..0abe73fa6e3 100644 --- a/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/TypeElementMembers.java +++ b/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/TypeElementMembers.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2016 the original author or authors. + * Copyright 2012-2017 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. @@ -171,6 +171,22 @@ class TypeElementMembers { return Collections.unmodifiableMap(this.publicGetters); } + public ExecutableElement getPublicGetter(String name, TypeMirror type) { + ExecutableElement candidate = this.publicGetters.get(name); + if (candidate != null) { + TypeMirror returnType = candidate.getReturnType(); + if (this.env.getTypeUtils().isSameType(returnType, type)) { + return candidate; + } + TypeMirror alternative = this.typeUtils.getWrapperOrPrimitiveFor(type); + if (alternative != null && + this.env.getTypeUtils().isSameType(returnType, alternative)) { + return candidate; + } + } + return null; + } + public ExecutableElement getPublicSetter(String name, TypeMirror type) { List candidates = this.publicSetters.get(name); if (candidates != null) { diff --git a/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/metadata/ItemMetadata.java b/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/metadata/ItemMetadata.java index c1a03dc57ef..d7aa5296454 100644 --- a/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/metadata/ItemMetadata.java +++ b/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/metadata/ItemMetadata.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2014 the original author or authors. + * Copyright 2012-2017 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. @@ -24,7 +24,7 @@ package org.springframework.boot.configurationprocessor.metadata; * @since 1.2.0 * @see ConfigurationMetadata */ -public class ItemMetadata implements Comparable { +public final class ItemMetadata implements Comparable { private ItemType itemType; @@ -143,13 +143,59 @@ public class ItemMetadata implements Comparable { return string.toString(); } - protected final void buildToStringProperty(StringBuilder string, String property, + protected void buildToStringProperty(StringBuilder string, String property, Object value) { if (value != null) { string.append(" ").append(property).append(":").append(value); } } + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + ItemMetadata other = (ItemMetadata) o; + return nullSafeEquals(this.itemType, other.itemType) + && nullSafeEquals(this.name, other.name) + && nullSafeEquals(this.type, other.type) + && nullSafeEquals(this.description, other.description) + && nullSafeEquals(this.sourceType, other.sourceType) + && nullSafeEquals(this.sourceMethod, other.sourceMethod) + && nullSafeEquals(this.defaultValue, other.defaultValue) + && nullSafeEquals(this.deprecation, other.deprecation); + } + + @Override + public int hashCode() { + int result = nullSafeHashCode(this.itemType); + result = 31 * result + nullSafeHashCode(this.name); + result = 31 * result + nullSafeHashCode(this.type); + result = 31 * result + nullSafeHashCode(this.description); + result = 31 * result + nullSafeHashCode(this.sourceType); + result = 31 * result + nullSafeHashCode(this.sourceMethod); + result = 31 * result + nullSafeHashCode(this.defaultValue); + result = 31 * result + nullSafeHashCode(this.deprecation); + return result; + } + + private boolean nullSafeEquals(Object o1, Object o2) { + if (o1 == o2) { + return true; + } + if (o1 == null || o2 == null) { + return false; + } + return o1.equals(o2); + } + + private int nullSafeHashCode(Object o) { + return (o == null ? 0 : o.hashCode()); + } + @Override public int compareTo(ItemMetadata o) { return getName().compareTo(o.getName()); diff --git a/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessorTests.java b/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessorTests.java index a145f0361e7..7c36f3995db 100644 --- a/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessorTests.java +++ b/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessorTests.java @@ -40,6 +40,7 @@ import org.springframework.boot.configurationsample.incremental.FooProperties; import org.springframework.boot.configurationsample.incremental.RenamedBarProperties; import org.springframework.boot.configurationsample.lombok.LombokExplicitProperties; import org.springframework.boot.configurationsample.lombok.LombokInnerClassProperties; +import org.springframework.boot.configurationsample.lombok.LombokInnerClassWithGetterProperties; import org.springframework.boot.configurationsample.lombok.LombokSimpleDataProperties; import org.springframework.boot.configurationsample.lombok.LombokSimpleProperties; import org.springframework.boot.configurationsample.lombok.SimpleLombokPojo; @@ -487,6 +488,20 @@ public class ConfigurationMetadataAnnotationProcessorTests { assertThat(metadata).isNotEqualTo(Metadata.withGroup("config.fourth")); } + @Test + public void lombokInnerClassWithGetterProperties() throws IOException { + ConfigurationMetadata metadata = + compile(LombokInnerClassWithGetterProperties.class); + assertThat(metadata).has(Metadata.withGroup("config") + .fromSource(LombokInnerClassWithGetterProperties.class)); + assertThat(metadata).has(Metadata.withGroup("config.first") + .ofType(LombokInnerClassWithGetterProperties.Foo.class) + .fromSourceMethod("getFirst()") + .fromSource(LombokInnerClassWithGetterProperties.class)); + assertThat(metadata).has(Metadata.withProperty("config.first.name")); + assertThat(metadata.getItems()).hasSize(3); + } + @Test public void mergingOfAdditionalProperty() throws Exception { ItemMetadata property = ItemMetadata.newProperty(null, "foo", "java.lang.String", diff --git a/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/Metadata.java b/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/Metadata.java index ef99e535f53..5d40fc9cefa 100644 --- a/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/Metadata.java +++ b/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/Metadata.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2016 the original author or authors. + * Copyright 2012-2017 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. @@ -80,6 +80,8 @@ public final class Metadata { private final Class sourceType; + private final String sourceMethod; + private final String description; private final Object defaultValue; @@ -87,16 +89,17 @@ public final class Metadata { private final ItemDeprecation deprecation; public MetadataItemCondition(ItemType itemType, String name) { - this(itemType, name, null, null, null, null, null); + this(itemType, name, null, null, null, null, null, null); } public MetadataItemCondition(ItemType itemType, String name, String type, - Class sourceType, String description, Object defaultValue, - ItemDeprecation deprecation) { + Class sourceType, String sourceMethod, String description, + Object defaultValue, ItemDeprecation deprecation) { this.itemType = itemType; this.name = name; this.type = type; this.sourceType = sourceType; + this.sourceMethod = sourceMethod; this.description = description; this.defaultValue = defaultValue; this.deprecation = deprecation; @@ -112,6 +115,9 @@ public final class Metadata { if (this.sourceType != null) { description.append(" with sourceType:").append(this.sourceType); } + if (this.sourceMethod != null) { + description.append(" with sourceMethod:").append(this.sourceMethod); + } if (this.defaultValue != null) { description.append(" with defaultValue:").append(this.defaultValue); } @@ -137,6 +143,10 @@ public final class Metadata { && !this.sourceType.getName().equals(itemMetadata.getSourceType())) { return false; } + if (this.sourceMethod != null + && !this.sourceMethod.equals(itemMetadata.getSourceMethod())) { + return false; + } if (this.defaultValue != null && !ObjectUtils .nullSafeEquals(this.defaultValue, itemMetadata.getDefaultValue())) { return false; @@ -157,40 +167,50 @@ public final class Metadata { public MetadataItemCondition ofType(Class dataType) { return new MetadataItemCondition(this.itemType, this.name, dataType.getName(), - this.sourceType, this.description, this.defaultValue, - this.deprecation); + this.sourceType, this.sourceMethod, this.description, + this.defaultValue, this.deprecation); } public MetadataItemCondition ofType(String dataType) { return new MetadataItemCondition(this.itemType, this.name, dataType, - this.sourceType, this.description, this.defaultValue, - this.deprecation); + this.sourceType, this.sourceMethod, this.description, + this.defaultValue, this.deprecation); } public MetadataItemCondition fromSource(Class sourceType) { return new MetadataItemCondition(this.itemType, this.name, this.type, - sourceType, this.description, this.defaultValue, this.deprecation); + sourceType, this.sourceMethod, this.description, this.defaultValue, + this.deprecation); + } + + public MetadataItemCondition fromSourceMethod(String sourceMethod) { + return new MetadataItemCondition(this.itemType, this.name, this.type, + this.sourceType, sourceMethod, this.description, this.defaultValue, + this.deprecation); } public MetadataItemCondition withDescription(String description) { return new MetadataItemCondition(this.itemType, this.name, this.type, - this.sourceType, description, this.defaultValue, this.deprecation); + this.sourceType, this.sourceMethod, description, this.defaultValue, + this.deprecation); } public MetadataItemCondition withDefaultValue(Object defaultValue) { return new MetadataItemCondition(this.itemType, this.name, this.type, - this.sourceType, this.description, defaultValue, this.deprecation); + this.sourceType, this.sourceMethod, this.description, defaultValue, + this.deprecation); } public MetadataItemCondition withDeprecation(String reason, String replacement) { return new MetadataItemCondition(this.itemType, this.name, this.type, - this.sourceType, this.description, this.defaultValue, - new ItemDeprecation(reason, replacement)); + this.sourceType, this.sourceMethod, this.description, + this.defaultValue, new ItemDeprecation(reason, replacement)); } public MetadataItemCondition withNoDeprecation() { return new MetadataItemCondition(this.itemType, this.name, this.type, - this.sourceType, this.description, this.defaultValue, null); + this.sourceType, this.sourceMethod, this.description, + this.defaultValue, null); } private ItemMetadata getFirstItemWithName(ConfigurationMetadata metadata, diff --git a/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/lombok/LombokInnerClassWithGetterProperties.java b/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/lombok/LombokInnerClassWithGetterProperties.java new file mode 100644 index 00000000000..03c24e7e0e1 --- /dev/null +++ b/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/lombok/LombokInnerClassWithGetterProperties.java @@ -0,0 +1,41 @@ +/* + * Copyright 2012-2017 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 lombok.Data; + +import org.springframework.boot.configurationsample.ConfigurationProperties; + +@Data +@ConfigurationProperties(prefix = "config") +@SuppressWarnings("unused") +public class LombokInnerClassWithGetterProperties { + + private final Foo first = new Foo(); + + public Foo getFirst() { + return this.first; + } + + @Data + public static class Foo { + + private String name; + + } + +}