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 82ecb4e17b..d1fea03829 100644 --- a/processor/src/main/java/org/mapstruct/ap/spi/DefaultAccessorNamingStrategy.java +++ b/processor/src/main/java/org/mapstruct/ap/spi/DefaultAccessorNamingStrategy.java @@ -5,13 +5,17 @@ */ package org.mapstruct.ap.spi; +import java.util.List; import java.util.regex.Pattern; +import javax.lang.model.element.Element; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.TypeElement; +import javax.lang.model.element.TypeParameterElement; import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; +import javax.lang.model.type.TypeVariable; import javax.lang.model.util.Elements; import javax.lang.model.util.SimpleElementVisitor6; import javax.lang.model.util.SimpleTypeVisitor6; @@ -102,10 +106,48 @@ public boolean isSetterMethod(ExecutableElement method) { } protected boolean isFluentSetter(ExecutableElement method) { + TypeMirror returnType = method.getReturnType(); + Element enclosingElement = method.getEnclosingElement(); + + TypeElement enclosingTypeElement = (TypeElement) enclosingElement; + + if ( returnType.getKind() == TypeKind.TYPEVAR + && isTypeVariableRepresentingBuilder( returnType, enclosingTypeElement ) ) { + returnType = enclosingTypeElement.asType(); + } + return method.getParameters().size() == 1 && - !JAVA_JAVAX_PACKAGE.matcher( method.getEnclosingElement().asType().toString() ).matches() && + !JAVA_JAVAX_PACKAGE.matcher( enclosingElement.asType().toString() ).matches() && !isAdderWithUpperCase4thCharacter( method ) && - typeUtils.isAssignable( method.getReturnType(), method.getEnclosingElement().asType() ); + typeUtils.isAssignable( returnType, enclosingElement.asType() ); + } + + private boolean isTypeVariableRepresentingBuilder(TypeMirror typeVariable, TypeElement builderElement) { + if ( typeVariable.getKind() != TypeKind.TYPEVAR ) { + return false; + } + + String typeVarName = ((TypeVariable) typeVariable).asElement().getSimpleName().toString(); + + TypeElement currentElement = builderElement; + while ( currentElement != null ) { + List typeParameters = currentElement.getTypeParameters(); + + for ( TypeParameterElement typeParam : typeParameters ) { + if ( typeParam.getSimpleName().contentEquals( typeVarName ) ) { + return true; + } + } + + TypeMirror superclass = currentElement.getSuperclass(); + if ( superclass.getKind() == TypeKind.NONE + || superclass.toString().equals( Object.class.getCanonicalName() ) ) { + break; + } + currentElement = (TypeElement) ( (DeclaredType) superclass ).asElement(); + } + + return false; } /** diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3707/AbstractDto.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3707/AbstractDto.java new file mode 100644 index 0000000000..08ac0a5423 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3707/AbstractDto.java @@ -0,0 +1,43 @@ +/* + * 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._3707; + +public abstract class AbstractDto { + private final String name; + private final Integer id; + + public abstract static class Builder { + private String name; + private Integer id; + + protected abstract T self(); + + public T name(String name) { + this.name = name; + return self(); + } + + public T id(Integer id) { + this.id = id; + return self(); + } + + public abstract AbstractDto build(); + } + + protected AbstractDto(Builder builder) { + this.name = builder.name; + this.id = builder.id; + } + + public String getName() { + return name; + } + + public int getId() { + return id; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3707/BigIntegerDto.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3707/BigIntegerDto.java new file mode 100644 index 0000000000..053e592e9c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3707/BigIntegerDto.java @@ -0,0 +1,45 @@ +/* + * 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._3707; + +import java.math.BigInteger; + +public class BigIntegerDto extends AbstractDto { + private final BigInteger value; + + public static class Builder extends AbstractDto.Builder { + + private BigInteger value; + + @Override + protected Builder self() { + return this; + } + + public Builder value(BigInteger value) { + this.value = value; + return self(); + } + + @Override + public BigIntegerDto build() { + return new BigIntegerDto( this ); + } + } + + private BigIntegerDto(Builder builder) { + super( builder ); + value = builder.value; + } + + public BigInteger getValue() { + return value; + } + + public static Builder builder() { + return new Builder(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3707/Issue3707Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3707/Issue3707Mapper.java new file mode 100644 index 0000000000..369843ae20 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3707/Issue3707Mapper.java @@ -0,0 +1,19 @@ +/* + * 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._3707; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface Issue3707Mapper { + + Issue3707Mapper INSTANCE = Mappers.getMapper( Issue3707Mapper.class ); + + StringDto clone(StringDto stringDto); + + BigIntegerDto clone(BigIntegerDto bigIntegerDto); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3707/Issue3707Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3707/Issue3707Test.java new file mode 100644 index 0000000000..bfab2a23fd --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3707/Issue3707Test.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._3707; + +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.runner.GeneratedSource; + +@WithClasses({ + Issue3707Mapper.class, + AbstractDto.class, + BigIntegerDto.class, + StringDto.class, +}) +@IssueKey("3707") +public class Issue3707Test { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource() + .addComparisonToFixtureFor( Issue3707Mapper.class ); + + @ProcessorTest + void shouldCompile() { + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3707/StringDto.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3707/StringDto.java new file mode 100644 index 0000000000..a7aaf8a686 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3707/StringDto.java @@ -0,0 +1,51 @@ +/* + * 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._3707; + +public class StringDto extends AbstractDto { + private final String value; + + public static class Builder extends AbstractDto.Builder { + + private String value; + + @Override + protected Builder self() { + return this; + } + + public Builder value(String value) { + this.value = value; + return self(); + } + + public Builder name(String value) { + return super.name( value ); + } + + public Builder id(Integer value) { + return super.id( value ); + } + + @Override + public StringDto build() { + return new StringDto( this ); + } + } + + private StringDto(Builder builder) { + super( builder ); + value = builder.value; + } + + public String getValue() { + return value; + } + + public static Builder builder() { + return new Builder(); + } +} diff --git a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/bugs/_3707/Issue3707MapperImpl.java b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/bugs/_3707/Issue3707MapperImpl.java new file mode 100644 index 0000000000..7338e77ef2 --- /dev/null +++ b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/bugs/_3707/Issue3707MapperImpl.java @@ -0,0 +1,46 @@ +/* + * 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._3707; + +import javax.annotation.processing.Generated; + +@Generated( + value = "org.mapstruct.ap.MappingProcessor", + date = "2024-10-19T17:05:14+0300", + comments = "version: , compiler: javac, environment: Java 17.0.10 (Private Build)" +) +public class Issue3707MapperImpl implements Issue3707Mapper { + + @Override + public StringDto clone(StringDto stringDto) { + if ( stringDto == null ) { + return null; + } + + StringDto.Builder stringDto1 = StringDto.builder(); + + stringDto1.value( stringDto.getValue() ); + stringDto1.name( stringDto.getName() ); + stringDto1.id( stringDto.getId() ); + + return stringDto1.build(); + } + + @Override + public BigIntegerDto clone(BigIntegerDto bigIntegerDto) { + if ( bigIntegerDto == null ) { + return null; + } + + BigIntegerDto.Builder bigIntegerDto1 = BigIntegerDto.builder(); + + bigIntegerDto1.name( bigIntegerDto.getName() ); + bigIntegerDto1.id( bigIntegerDto.getId() ); + bigIntegerDto1.value( bigIntegerDto.getValue() ); + + return bigIntegerDto1.build(); + } +}