From 318b30ef236214602f28115f2f07cda5b882399d Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sun, 22 Sep 2019 18:22:11 +0200 Subject: [PATCH] #1799 Fluent setters starting with set should work properly --- .../ap/spi/DefaultAccessorNamingStrategy.java | 28 +++++++--- .../ap/test/bugs/_1799/Issue1799Mapper.java | 20 +++++++ .../ap/test/bugs/_1799/Issue1799Test.java | 37 +++++++++++++ .../mapstruct/ap/test/bugs/_1799/Source.java | 30 +++++++++++ .../mapstruct/ap/test/bugs/_1799/Target.java | 54 +++++++++++++++++++ 5 files changed, 163 insertions(+), 6 deletions(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_1799/Issue1799Mapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_1799/Issue1799Test.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_1799/Source.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_1799/Target.java diff --git a/processor/src/main/java/org/mapstruct/ap/spi/DefaultAccessorNamingStrategy.java b/processor/src/main/java/org/mapstruct/ap/spi/DefaultAccessorNamingStrategy.java index 6a8c9baaf..c3c14deb0 100644 --- a/processor/src/main/java/org/mapstruct/ap/spi/DefaultAccessorNamingStrategy.java +++ b/processor/src/main/java/org/mapstruct/ap/spi/DefaultAccessorNamingStrategy.java @@ -57,7 +57,8 @@ public class DefaultAccessorNamingStrategy implements AccessorNamingStrategy { } /** - * Returns {@code true} when the {@link ExecutableElement} is a getter method. A method is a getter when it starts + * Returns {@code true} when the {@link ExecutableElement} is a getter method. A method is a getter when it + * has no parameters, starts * with 'get' and the return type is any type other than {@code void}, OR the getter starts with 'is' and the type * returned is a primitive or the wrapper for {@code boolean}. NOTE: the latter does strictly not comply to the bean * convention. The remainder of the name is supposed to reflect the property name. @@ -69,6 +70,10 @@ public class DefaultAccessorNamingStrategy implements AccessorNamingStrategy { * @return {@code true} when the method is a getter. */ public boolean isGetterMethod(ExecutableElement method) { + if ( !method.getParameters().isEmpty() ) { + // If the method has parameters it can't be a getter + return false; + } String methodName = method.getSimpleName().toString(); boolean isNonBooleanGetterName = methodName.startsWith( "get" ) && methodName.length() > 3 && @@ -166,11 +171,22 @@ public class DefaultAccessorNamingStrategy implements AccessorNamingStrategy { @Override public String getPropertyName(ExecutableElement getterOrSetterMethod) { String methodName = getterOrSetterMethod.getSimpleName().toString(); - if ( methodName.startsWith( "get" ) || methodName.startsWith( "set" ) ) { - return IntrospectorUtils.decapitalize( methodName.substring( 3 ) ); - } - else if ( isFluentSetter( getterOrSetterMethod ) ) { - return methodName; + if ( isFluentSetter( getterOrSetterMethod ) ) { + // If this is a fluent setter that starts with set and the 4th character is an uppercase one + // then we treat it as a Java Bean style method (we get the property starting from the 4th character). + // Otherwise we treat it as a fluent setter + // For example, for the following methods: + // * public Builder setSettlementDate(String settlementDate) + // * public Builder settlementDate(String settlementDate) + // We are going to extract the same property name settlementDate + if ( methodName.startsWith( "set" ) + && methodName.length() > 3 + && Character.isUpperCase( methodName.charAt( 3 ) ) ) { + return IntrospectorUtils.decapitalize( methodName.substring( 3 ) ); + } + else { + return methodName; + } } return IntrospectorUtils.decapitalize( methodName.substring( methodName.startsWith( "is" ) ? 2 : 3 ) ); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1799/Issue1799Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1799/Issue1799Mapper.java new file mode 100644 index 000000000..7b695dd14 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1799/Issue1799Mapper.java @@ -0,0 +1,20 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._1799; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface Issue1799Mapper { + + Issue1799Mapper INSTANCE = Mappers.getMapper( Issue1799Mapper.class ); + + Target map(Source source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1799/Issue1799Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1799/Issue1799Test.java new file mode 100644 index 000000000..742c72f71 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1799/Issue1799Test.java @@ -0,0 +1,37 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._1799; + +import java.util.Date; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@WithClasses({ + Issue1799Mapper.class, + Source.class, + Target.class, +}) +@IssueKey("1799") +@RunWith(AnnotationProcessorTestRunner.class) +public class Issue1799Test { + + @Test + public void fluentJavaBeanStyleSettersShouldWork() { + Target target = Issue1799Mapper.INSTANCE.map( new Source( new Date( 150 ), "Switzerland" ) ); + + assertThat( target.getSettlementDate() ).isEqualTo( new Date( 150 ) ); + assertThat( target.getGetawayLocation() ).isEqualTo( "Switzerland" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1799/Source.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1799/Source.java new file mode 100644 index 000000000..dc7153a2f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1799/Source.java @@ -0,0 +1,30 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._1799; + +import java.util.Date; + +/** + * @author Filip Hrisafov + */ +public class Source { + + private final Date settlementDate; + private final String getawayLocation; + + public Source(Date settlementDate, String getawayLocation) { + this.settlementDate = settlementDate; + this.getawayLocation = getawayLocation; + } + + public Date getSettlementDate() { + return settlementDate; + } + + public String getGetawayLocation() { + return getawayLocation; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1799/Target.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1799/Target.java new file mode 100644 index 000000000..992e3d228 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1799/Target.java @@ -0,0 +1,54 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._1799; + +import java.util.Date; + +/** + * @author Filip Hrisafov + */ +public class Target { + + private final Date settlementDate; + private final String getawayLocation; + + public Target(Builder builder) { + this.settlementDate = builder.settlementDate; + this.getawayLocation = builder.getawayLocation; + } + + public Date getSettlementDate() { + return settlementDate; + } + + public String getGetawayLocation() { + return getawayLocation; + } + + public static Target.Builder builder() { + return new Builder(); + } + + public static class Builder { + + private Date settlementDate; + private String getawayLocation; + + public Builder settlementDate(Date settlementDate) { + this.settlementDate = settlementDate; + return this; + } + + public Builder getawayLocation(String getawayLocation) { + this.getawayLocation = getawayLocation; + return this; + } + + public Target build() { + return new Target( this ); + } + } +}