diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java index 080eab1e76..c0700b634d 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java @@ -1693,6 +1693,15 @@ private void applyTargetThisMapping() { // apply name based mapping applyPropertyNameBasedMapping( sourceRefs ); + // the source of a target this mapping is only consumed when it actually contributed + // a property, so an unused target this still reports its source as unmapped + if ( !sourceRefs.isEmpty() ) { + String sourceName = targetThis.getSourceReference().getShallowestPropertyName(); + if ( sourceName != null ) { + unprocessedSourceProperties.remove( sourceName ); + } + } + // add handled target properties handledTargetProperties.addAll( sourceRefs.stream() .map( SourceReference::getDeepestPropertyName ) diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3839/ErroneousIssue3839UnusedTargetThisMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3839/ErroneousIssue3839UnusedTargetThisMapper.java new file mode 100644 index 0000000000..866a0c94b0 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3839/ErroneousIssue3839UnusedTargetThisMapper.java @@ -0,0 +1,56 @@ +/* + * 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._3839; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ReportingPolicy; + +@Mapper( unmappedSourcePolicy = ReportingPolicy.ERROR ) +public interface ErroneousIssue3839UnusedTargetThisMapper { + + @Mapping( target = ".", source = "record" ) + Target map(Source source); + + class Source { + + private Record record; + + public Record getRecord() { + return record; + } + + public void setRecord(Record record) { + this.record = record; + } + } + + class Record { + + private String alpha; + + public String getAlpha() { + return alpha; + } + + public void setAlpha(String alpha) { + this.alpha = alpha; + } + } + + class Target { + + private String zulu; + + public String getZulu() { + return zulu; + } + + public void setZulu(String zulu) { + this.zulu = zulu; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3839/Issue3839DocExampleMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3839/Issue3839DocExampleMapper.java new file mode 100644 index 0000000000..ecddbe368f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3839/Issue3839DocExampleMapper.java @@ -0,0 +1,124 @@ +/* + * 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._3839; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ReportingPolicy; + +/** + * The "target this" example from the reference documentation. + */ +@Mapper( unmappedSourcePolicy = ReportingPolicy.ERROR ) +public interface Issue3839DocExampleMapper { + + @Mapping( target = "name", source = "record.name" ) + @Mapping( target = ".", source = "record" ) + @Mapping( target = ".", source = "account" ) + Customer map(CustomerDto customerDto); + + class CustomerDto { + + private Record record; + + private Account account; + + public Record getRecord() { + return record; + } + + public void setRecord(Record record) { + this.record = record; + } + + public Account getAccount() { + return account; + } + + public void setAccount(Account account) { + this.account = account; + } + } + + class Record { + + private String name; + + private String description; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + } + + class Account { + + private String name; + + private long number; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public long getNumber() { + return number; + } + + public void setNumber(long number) { + this.number = number; + } + } + + class Customer { + + private String name; + + private String description; + + private long number; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public long getNumber() { + return number; + } + + public void setNumber(long number) { + this.number = number; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3839/Issue3839Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3839/Issue3839Mapper.java new file mode 100644 index 0000000000..7e436b394a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3839/Issue3839Mapper.java @@ -0,0 +1,79 @@ +/* + * 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._3839; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ReportingPolicy; + +/** + * A single {@code target = "."} mapping whose source contributes both target properties. + */ +@Mapper( unmappedSourcePolicy = ReportingPolicy.ERROR ) +public interface Issue3839Mapper { + + @Mapping( target = ".", source = "record" ) + Customer map(CustomerDto customerDto); + + class CustomerDto { + + private Record record; + + public Record getRecord() { + return record; + } + + public void setRecord(Record record) { + this.record = record; + } + } + + class Record { + + private String name; + + private String description; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + } + + class Customer { + + private String name; + + private String description; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3839/Issue3839Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3839/Issue3839Test.java new file mode 100644 index 0000000000..a1508478d4 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3839/Issue3839Test.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._3839; + +import javax.tools.Diagnostic.Kind; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult; +import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic; +import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome; + +/** + * A source consumed by {@code @Mapping( target = "." )} should count as mapped for + * {@code unmappedSourcePolicy}, unless the target this mapping contributed nothing. + */ +@IssueKey( "3839" ) +public class Issue3839Test { + + @ProcessorTest + @WithClasses( Issue3839Mapper.class ) + public void targetThisSourceIsNotReportedAsUnmapped() { + } + + @ProcessorTest + @WithClasses( Issue3839DocExampleMapper.class ) + public void documentedTargetThisExampleCompiles() { + } + + @ProcessorTest + @WithClasses( ErroneousIssue3839UnusedTargetThisMapper.class ) + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic(type = ErroneousIssue3839UnusedTargetThisMapper.class, + kind = Kind.WARNING, + line = 16, + message = "Unmapped target property: \"zulu\"."), + @Diagnostic(type = ErroneousIssue3839UnusedTargetThisMapper.class, + kind = Kind.ERROR, + line = 16, + message = "Unmapped source property: \"record\".") + } + ) + public void unusedTargetThisStillReportsItsSource() { + } +}