#2538 Allow using 2 step mappings with only one of the 2 methods being qualified

This commit is contained in:
Filip Hrisafov 2022-03-12 23:57:17 +01:00 committed by GitHub
parent 0a69492983
commit ad00adfa86
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 215 additions and 5 deletions

View File

@ -6,6 +6,7 @@
package org.mapstruct.ap.internal.model.source.selector;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.lang.model.type.TypeMirror;
@ -26,6 +27,7 @@ public class SelectionCriteria {
private final String targetPropertyName;
private final TypeMirror qualifyingResultType;
private final SourceRHS sourceRHS;
private boolean ignoreQualifiers = false;
private Type type;
private final boolean allowDirect;
private final boolean allowConversion;
@ -87,12 +89,16 @@ public class SelectionCriteria {
return type == Type.PRESENCE_CHECK;
}
public void setIgnoreQualifiers(boolean ignoreQualifiers) {
this.ignoreQualifiers = ignoreQualifiers;
}
public List<TypeMirror> getQualifiers() {
return qualifiers;
return ignoreQualifiers ? Collections.emptyList() : qualifiers;
}
public List<String> getQualifiedByNames() {
return qualifiedByNames;
return ignoreQualifiers ? Collections.emptyList() : qualifiedByNames;
}
public String getTargetPropertyName() {

View File

@ -712,6 +712,11 @@ public class MappingResolverImpl implements MappingResolver {
}
}
private enum BestMatchType {
IGNORE_QUALIFIERS_BEFORE_Y_CANDIDATES,
IGNORE_QUALIFIERS_AFTER_Y_CANDIDATES,
}
/**
* Suppose mapping required from A to C and:
* <ul>
@ -743,6 +748,17 @@ public class MappingResolverImpl implements MappingResolver {
if ( mmAttempt.hasResult ) {
return mmAttempt.result;
}
if ( att.hasQualfiers() ) {
mmAttempt = mmAttempt.getBestMatchIgnoringQualifiersBeforeY( sourceType, targetType );
if ( mmAttempt.hasResult ) {
return mmAttempt.result;
}
mmAttempt = mmAttempt.getBestMatchIgnoringQualifiersAfterY( sourceType, targetType );
if ( mmAttempt.hasResult ) {
return mmAttempt.result;
}
}
MethodMethod<Method, BuiltInMethod> mbAttempt =
new MethodMethod<>( att, att.methods, att.builtIns, att::toMethodRef, att::toBuildInRef )
.getBestMatch( sourceType, targetType );
@ -772,6 +788,18 @@ public class MappingResolverImpl implements MappingResolver {
}
private MethodMethod<T1, T2> getBestMatch(Type sourceType, Type targetType) {
return getBestMatch( sourceType, targetType, null );
}
private MethodMethod<T1, T2> getBestMatchIgnoringQualifiersBeforeY(Type sourceType, Type targetType) {
return getBestMatch( sourceType, targetType, BestMatchType.IGNORE_QUALIFIERS_BEFORE_Y_CANDIDATES );
}
private MethodMethod<T1, T2> getBestMatchIgnoringQualifiersAfterY(Type sourceType, Type targetType) {
return getBestMatch( sourceType, targetType, BestMatchType.IGNORE_QUALIFIERS_AFTER_Y_CANDIDATES );
}
private MethodMethod<T1, T2> getBestMatch(Type sourceType, Type targetType, BestMatchType matchType) {
Set<T2> yCandidates = new HashSet<>();
Map<SelectedMethod<T1>, List<SelectedMethod<T2>>> xCandidates = new LinkedHashMap<>();
@ -784,6 +812,9 @@ public class MappingResolverImpl implements MappingResolver {
// sourceMethod or builtIn that fits the signature B to C. Only then there is a match. If we have a match
// a nested method call can be called. so C = methodY( methodX (A) )
attempt.selectionCriteria.setPreferUpdateMapping( false );
attempt.selectionCriteria.setIgnoreQualifiers(
matchType == BestMatchType.IGNORE_QUALIFIERS_BEFORE_Y_CANDIDATES );
for ( T2 yCandidate : yMethods ) {
Type ySourceType = yCandidate.getMappingSourceType();
ySourceType = ySourceType.resolveParameterToType( targetType, yCandidate.getResultType() ).getMatch();
@ -796,13 +827,16 @@ public class MappingResolverImpl implements MappingResolver {
}
List<SelectedMethod<T1>> xMatches = attempt.getBestMatch( xMethods, sourceType, ySourceType );
if ( !xMatches.isEmpty() ) {
xMatches.stream().forEach( x -> xCandidates.put( x, new ArrayList<>() ) );
final Type typeInTheMiddle = ySourceType;
xMatches.stream().forEach( x -> typesInTheMiddle.put( x, typeInTheMiddle ) );
for ( SelectedMethod<T1> x : xMatches ) {
xCandidates.put( x, new ArrayList<>() );
typesInTheMiddle.put( x, ySourceType );
}
yCandidates.add( yCandidate );
}
}
attempt.selectionCriteria.setPreferUpdateMapping( true );
attempt.selectionCriteria.setIgnoreQualifiers(
matchType == BestMatchType.IGNORE_QUALIFIERS_AFTER_Y_CANDIDATES );
// collect all results
List<T2> yCandidatesList = new ArrayList<>( yCandidates );
@ -816,6 +850,7 @@ public class MappingResolverImpl implements MappingResolver {
}
}
attempt.selectionCriteria.setIgnoreQualifiers( false );
// no results left
if ( xCandidates.isEmpty() ) {
return this;

View File

@ -0,0 +1,22 @@
/*
* 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._2538;
/**
* @author Filip Hrisafov
*/
public class Group {
private final String id;
public Group(String id) {
this.id = id;
}
public String getId() {
return id;
}
}

View File

@ -0,0 +1,22 @@
/*
* 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._2538;
/**
* @author Filip Hrisafov
*/
public class GroupDto {
private final String id;
public GroupDto(String id) {
this.id = id;
}
public String getId() {
return id;
}
}

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.bugs._2538;
import org.mapstruct.ap.testutil.ProcessorTest;
import org.mapstruct.ap.testutil.WithClasses;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Filip Hrisafov
*/
@WithClasses({
Group.class,
GroupDto.class,
TeamMapper.class,
TeamRole.class,
TeamRoleDto.class,
})
class Issue2538Test {
@ProcessorTest
void shouldCorrectlyUseQualifiedMethodIn2StepMapping() {
TeamRole role = TeamMapper.INSTANCE.mapUsingFirstLookup( new TeamRoleDto( "test" ) );
assertThat( role ).isNotNull();
assertThat( role.getGroup() ).isNotNull();
assertThat( role.getGroup().getId() ).isEqualTo( "lookup-test" );
role = TeamMapper.INSTANCE.mapUsingSecondLookup( new TeamRoleDto( "test" ) );
assertThat( role ).isNotNull();
assertThat( role.getGroup() ).isNotNull();
assertThat( role.getGroup().getId() ).isEqualTo( "second-test" );
}
}

View File

@ -0,0 +1,42 @@
/*
* 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._2538;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Named;
import org.mapstruct.factory.Mappers;
@Mapper
public interface TeamMapper {
TeamMapper INSTANCE = Mappers.getMapper( TeamMapper.class );
// This method is testing methodX(methodY(...)) where methodY is qualified
@Mapping(target = "group", source = "groupId", qualifiedByName = "firstLookup")
TeamRole mapUsingFirstLookup(TeamRoleDto in);
// This method is testing methodX(methodY(...)) where methodX is qualified
@Mapping(target = "group", source = "groupId", qualifiedByName = "secondLookup")
TeamRole mapUsingSecondLookup(TeamRoleDto in);
Group map(GroupDto in);
@Named("firstLookup")
default GroupDto lookupGroup(String groupId) {
return groupId != null ? new GroupDto( "lookup-" + groupId ) : null;
}
default GroupDto normalLookup(String groupId) {
return groupId != null ? new GroupDto( groupId ) : null;
}
@Named("secondLookup")
default Group mapSecondLookup(GroupDto in) {
return in != null ? new Group( "second-" + in.getId() ) : null;
}
}

View File

@ -0,0 +1,22 @@
/*
* 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._2538;
/**
* @author Filip Hrisafov
*/
public class TeamRole {
private final Group group;
public TeamRole(Group group) {
this.group = group;
}
public Group getGroup() {
return group;
}
}

View File

@ -0,0 +1,22 @@
/*
* 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._2538;
/**
* @author Filip Hrisafov
*/
public class TeamRoleDto {
private final String groupId;
public TeamRoleDto(String groupId) {
this.groupId = groupId;
}
public String getGroupId() {
return groupId;
}
}