#2206, #2214, #2220: Source property should be correctly determined when only target is defined

When having multiple source properties and only target is defined then the same rules should be applied as if there was no mapping:

* First we check for a matching property in any of the source type
* Second we check if the parameter name matches
This commit is contained in:
Filip Hrisafov 2020-10-07 21:42:26 +02:00
parent a5f49e591e
commit 53a5c34ed6
6 changed files with 141 additions and 8 deletions

View File

@ -1070,8 +1070,36 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
SourceReference sourceRef = mappingRef.getSourceReference();
// sourceRef is not defined, check if a source property has the same name
if ( sourceRef == null && method.getSourceParameters().size() == 1 ) {
sourceRef = getSourceRefByTargetName( method.getSourceParameters().get( 0 ), targetPropertyName );
if ( sourceRef == null ) {
// Here we follow the same rules as when we implicitly map
// When we implicitly map we first do property name based mapping
// i.e. look for matching properties in the source types
// and then do parameter name based mapping
for ( Parameter sourceParameter : method.getSourceParameters() ) {
SourceReference matchingSourceRef = getSourceRefByTargetName(
sourceParameter,
targetPropertyName
);
if ( matchingSourceRef != null ) {
if ( sourceRef != null ) {
errorOccured = true;
// This can only happen when the target property matches multiple properties
// within the different source parameters
ctx.getMessager()
.printMessage(
method.getExecutable(),
mappingRef.getMapping().getMirror(),
Message.BEANMAPPING_SEVERAL_POSSIBLE_SOURCES,
targetPropertyName
);
break;
}
// We can't break here since it is possible that the same property exists in multiple
// source parameters
sourceRef = matchingSourceRef;
}
}
}
if ( sourceRef == null ) {

View File

@ -15,11 +15,18 @@ public interface Issue2125Mapper {
Issue2125Mapper INSTANCE = Mappers.getMapper( Issue2125Mapper.class );
// In this case the issueId from the Comment is used
Comment clone(Comment comment, Integer issueId);
// When source is not defined then we will use the issueId from the Comment,
// same as when there was no mapping
@Mapping(target = "issueId", qualifiedByName = "mapIssueNumber")
Comment cloneWithQualifier(Comment comment, Integer issueId);
// When source is defined then we will source the parameter name
@Mapping(target = "issueId", source = "issueId", qualifiedByName = "mapIssueNumber")
Comment cloneWithQualifierExplicitSource(Comment comment, Integer issueId);
@Named("mapIssueNumber")
default Integer mapIssueNumber(Integer issueNumber) {
return issueNumber != null ? issueNumber + 1 : null;

View File

@ -43,6 +43,14 @@ public class Issue2125Test {
1000
);
assertThat( comment ).isNotNull();
assertThat( comment.getIssueId() ).isEqualTo( 2126 );
comment = Issue2125Mapper.INSTANCE.cloneWithQualifierExplicitSource(
new Comment( 2125, "Fix issue" ),
1000
);
assertThat( comment ).isNotNull();
assertThat( comment.getIssueId() ).isEqualTo( 1001 );
}
@ -59,12 +67,6 @@ public class Issue2125Test {
alternativeLine = 17, // For some reason javac reports the error on the method instead of the annotation
message = "The type of parameter \"repository\" has no property named \"issueId\". Please define the " +
"source property explicitly."),
@Diagnostic(type = Issue2125ErroneousMapper.class,
kind = javax.tools.Diagnostic.Kind.ERROR,
line = 19,
alternativeLine = 21, // For some reason javac reports the error on the method instead of the annotation
message = "No property named \"issueId\" exists in source parameter(s). Please define the source " +
"explicitly.")
})
public void shouldReportErrorWhenMultipleSourcesMatch() {
}

View File

@ -0,0 +1,23 @@
/*
* 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.defaultvalue;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;
/**
* @author Filip Hrisafov
*/
@Mapper
public interface CountryMapperMultipleSources {
CountryMapperMultipleSources INSTANCE = Mappers.getMapper( CountryMapperMultipleSources.class );
@Mapping(target = "code", defaultValue = "CH")
@Mapping(target = "region", source = "regionCode")
CountryDts map(CountryEntity entity, String regionCode);
}

View File

@ -159,4 +159,38 @@ public class DefaultValueTest {
public void errorOnDefaultValueAndExpression() {
}
@Test
@IssueKey("2214")
@WithClasses({
CountryMapperMultipleSources.class,
Region.class,
})
public void shouldBeAbleToDetermineDefaultValueBasedOnOnlyTargetType() {
CountryEntity entity = new CountryEntity();
CountryDts target = CountryMapperMultipleSources.INSTANCE.map( entity, "ZH" );
assertThat( target ).isNotNull();
assertThat( target.getCode() ).isEqualTo( "CH" );
}
@Test
@IssueKey("2220")
@WithClasses({
ErroneousMissingSourceMapper.class,
Region.class,
})
@ExpectedCompilationOutcome(
value = CompilationResult.FAILED,
diagnostics = {
@Diagnostic(
type = ErroneousMissingSourceMapper.class,
kind = javax.tools.Diagnostic.Kind.ERROR,
line = 17,
message = "The type of parameter \"tenant\" has no property named \"type\"." +
" Please define the source property explicitly."),
}
)
public void errorWhenOnlyTargetDefinedAndSourceDoesNotHaveProperty() {
}
}

View File

@ -0,0 +1,39 @@
/*
* 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.defaultvalue;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
/**
* @author Filip Hrisafov
*/
@Mapper
public interface ErroneousMissingSourceMapper {
@Mapping(target = "type", defaultValue = "STANDARD")
Tenant map(TenantDto tenant);
class Tenant {
private final String id;
private final String type;
public Tenant(String id, String type) {
this.id = id;
this.type = type;
}
}
class TenantDto {
private final String id;
public TenantDto(String id) {
this.id = id;
}
}
}