Accommodate with Lombok generation ordering

Previously, if lombok was running before the configuration metadata
annotation processor, duplicated keys were created as both the
getter/setter and the special lombok handling applied.

This commit makes sure to be lenient by removing duplicate metadata
entries. This commit also makes sure to identify the getter of a
nested group if present. That way, the sourceMethod is set consistently
and avoid the creation of a duplicate group.

Closes gh-8886
This commit is contained in:
Stephane Nicoll 2017-05-12 16:48:31 +02:00
parent a2e749940e
commit 643dea18ee
7 changed files with 161 additions and 22 deletions

View File

@ -294,7 +294,8 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor
String name = entry.getKey(); String name = entry.getKey();
VariableElement field = entry.getValue(); VariableElement field = entry.getValue();
if (isLombokField(field, element)) { 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()); field.asType());
} }
} }

View File

@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -16,8 +16,8 @@
package org.springframework.boot.configurationprocessor; package org.springframework.boot.configurationprocessor;
import java.util.ArrayList;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
@ -39,7 +39,7 @@ import org.springframework.boot.configurationprocessor.metadata.ItemMetadata;
*/ */
public class MetadataCollector { public class MetadataCollector {
private final List<ItemMetadata> metadataItems = new ArrayList<ItemMetadata>(); private final Set<ItemMetadata> metadataItems = new LinkedHashSet<ItemMetadata>();
private final ProcessingEnvironment processingEnvironment; private final ProcessingEnvironment processingEnvironment;

View File

@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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); 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) { public ExecutableElement getPublicSetter(String name, TypeMirror type) {
List<ExecutableElement> candidates = this.publicSetters.get(name); List<ExecutableElement> candidates = this.publicSetters.get(name);
if (candidates != null) { if (candidates != null) {

View File

@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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 * @since 1.2.0
* @see ConfigurationMetadata * @see ConfigurationMetadata
*/ */
public class ItemMetadata implements Comparable<ItemMetadata> { public final class ItemMetadata implements Comparable<ItemMetadata> {
private ItemType itemType; private ItemType itemType;
@ -143,13 +143,59 @@ public class ItemMetadata implements Comparable<ItemMetadata> {
return string.toString(); return string.toString();
} }
protected final void buildToStringProperty(StringBuilder string, String property, protected void buildToStringProperty(StringBuilder string, String property,
Object value) { Object value) {
if (value != null) { if (value != null) {
string.append(" ").append(property).append(":").append(value); 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 @Override
public int compareTo(ItemMetadata o) { public int compareTo(ItemMetadata o) {
return getName().compareTo(o.getName()); return getName().compareTo(o.getName());

View File

@ -40,6 +40,7 @@ import org.springframework.boot.configurationsample.incremental.FooProperties;
import org.springframework.boot.configurationsample.incremental.RenamedBarProperties; import org.springframework.boot.configurationsample.incremental.RenamedBarProperties;
import org.springframework.boot.configurationsample.lombok.LombokExplicitProperties; import org.springframework.boot.configurationsample.lombok.LombokExplicitProperties;
import org.springframework.boot.configurationsample.lombok.LombokInnerClassProperties; 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.LombokSimpleDataProperties;
import org.springframework.boot.configurationsample.lombok.LombokSimpleProperties; import org.springframework.boot.configurationsample.lombok.LombokSimpleProperties;
import org.springframework.boot.configurationsample.lombok.SimpleLombokPojo; import org.springframework.boot.configurationsample.lombok.SimpleLombokPojo;
@ -487,6 +488,20 @@ public class ConfigurationMetadataAnnotationProcessorTests {
assertThat(metadata).isNotEqualTo(Metadata.withGroup("config.fourth")); 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 @Test
public void mergingOfAdditionalProperty() throws Exception { public void mergingOfAdditionalProperty() throws Exception {
ItemMetadata property = ItemMetadata.newProperty(null, "foo", "java.lang.String", ItemMetadata property = ItemMetadata.newProperty(null, "foo", "java.lang.String",

View File

@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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 Class<?> sourceType;
private final String sourceMethod;
private final String description; private final String description;
private final Object defaultValue; private final Object defaultValue;
@ -87,16 +89,17 @@ public final class Metadata {
private final ItemDeprecation deprecation; private final ItemDeprecation deprecation;
public MetadataItemCondition(ItemType itemType, String name) { 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, public MetadataItemCondition(ItemType itemType, String name, String type,
Class<?> sourceType, String description, Object defaultValue, Class<?> sourceType, String sourceMethod, String description,
ItemDeprecation deprecation) { Object defaultValue, ItemDeprecation deprecation) {
this.itemType = itemType; this.itemType = itemType;
this.name = name; this.name = name;
this.type = type; this.type = type;
this.sourceType = sourceType; this.sourceType = sourceType;
this.sourceMethod = sourceMethod;
this.description = description; this.description = description;
this.defaultValue = defaultValue; this.defaultValue = defaultValue;
this.deprecation = deprecation; this.deprecation = deprecation;
@ -112,6 +115,9 @@ public final class Metadata {
if (this.sourceType != null) { if (this.sourceType != null) {
description.append(" with sourceType:").append(this.sourceType); description.append(" with sourceType:").append(this.sourceType);
} }
if (this.sourceMethod != null) {
description.append(" with sourceMethod:").append(this.sourceMethod);
}
if (this.defaultValue != null) { if (this.defaultValue != null) {
description.append(" with defaultValue:").append(this.defaultValue); description.append(" with defaultValue:").append(this.defaultValue);
} }
@ -137,6 +143,10 @@ public final class Metadata {
&& !this.sourceType.getName().equals(itemMetadata.getSourceType())) { && !this.sourceType.getName().equals(itemMetadata.getSourceType())) {
return false; return false;
} }
if (this.sourceMethod != null
&& !this.sourceMethod.equals(itemMetadata.getSourceMethod())) {
return false;
}
if (this.defaultValue != null && !ObjectUtils if (this.defaultValue != null && !ObjectUtils
.nullSafeEquals(this.defaultValue, itemMetadata.getDefaultValue())) { .nullSafeEquals(this.defaultValue, itemMetadata.getDefaultValue())) {
return false; return false;
@ -157,40 +167,50 @@ public final class Metadata {
public MetadataItemCondition ofType(Class<?> dataType) { public MetadataItemCondition ofType(Class<?> dataType) {
return new MetadataItemCondition(this.itemType, this.name, dataType.getName(), return new MetadataItemCondition(this.itemType, this.name, dataType.getName(),
this.sourceType, this.description, this.defaultValue, this.sourceType, this.sourceMethod, this.description,
this.deprecation); this.defaultValue, this.deprecation);
} }
public MetadataItemCondition ofType(String dataType) { public MetadataItemCondition ofType(String dataType) {
return new MetadataItemCondition(this.itemType, this.name, dataType, return new MetadataItemCondition(this.itemType, this.name, dataType,
this.sourceType, this.description, this.defaultValue, this.sourceType, this.sourceMethod, this.description,
this.deprecation); this.defaultValue, this.deprecation);
} }
public MetadataItemCondition fromSource(Class<?> sourceType) { public MetadataItemCondition fromSource(Class<?> sourceType) {
return new MetadataItemCondition(this.itemType, this.name, this.type, 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) { public MetadataItemCondition withDescription(String description) {
return new MetadataItemCondition(this.itemType, this.name, this.type, 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) { public MetadataItemCondition withDefaultValue(Object defaultValue) {
return new MetadataItemCondition(this.itemType, this.name, this.type, 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) { public MetadataItemCondition withDeprecation(String reason, String replacement) {
return new MetadataItemCondition(this.itemType, this.name, this.type, return new MetadataItemCondition(this.itemType, this.name, this.type,
this.sourceType, this.description, this.defaultValue, this.sourceType, this.sourceMethod, this.description,
new ItemDeprecation(reason, replacement)); this.defaultValue, new ItemDeprecation(reason, replacement));
} }
public MetadataItemCondition withNoDeprecation() { public MetadataItemCondition withNoDeprecation() {
return new MetadataItemCondition(this.itemType, this.name, this.type, 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, private ItemMetadata getFirstItemWithName(ConfigurationMetadata metadata,

View File

@ -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;
}
}