#1714 Qualifiers should not qualfiy when no qualfier is found (#1739)

This commit is contained in:
Sjaak Derksen 2019-03-23 22:08:18 +01:00 committed by GitHub
parent f5ee2c6729
commit 6c838e6e0c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 229 additions and 85 deletions

View File

@ -37,7 +37,7 @@ public abstract class AbstractMappingMethodBuilder<B extends AbstractMappingMeth
} }
String name = getName( sourceType, targetType ); String name = getName( sourceType, targetType );
name = Strings.getSafeVariableName( name, ctx.getNamesOfMappingsToGenerate() ); name = Strings.getSafeVariableName( name, ctx.getReservedNames() );
ForgedMethodHistory history = null; ForgedMethodHistory history = null;
if ( method instanceof ForgedMethod ) { if ( method instanceof ForgedMethod ) {
history = ( (ForgedMethod) method ).getHistory(); history = ( (ForgedMethod) method ).getHistory();

View File

@ -19,6 +19,7 @@ import org.mapstruct.ap.internal.model.common.Type;
import org.mapstruct.ap.internal.model.source.ForgedMethod; import org.mapstruct.ap.internal.model.source.ForgedMethod;
import org.mapstruct.ap.internal.model.source.Method; import org.mapstruct.ap.internal.model.source.Method;
import org.mapstruct.ap.internal.model.source.SelectionParameters; import org.mapstruct.ap.internal.model.source.SelectionParameters;
import org.mapstruct.ap.internal.model.source.selector.SelectionCriteria;
import org.mapstruct.ap.internal.prism.NullValueMappingStrategyPrism; import org.mapstruct.ap.internal.prism.NullValueMappingStrategyPrism;
import org.mapstruct.ap.internal.util.Message; import org.mapstruct.ap.internal.util.Message;
import org.mapstruct.ap.internal.util.Strings; import org.mapstruct.ap.internal.util.Strings;
@ -82,18 +83,22 @@ public abstract class ContainerMappingMethodBuilder<B extends ContainerMappingMe
new HashSet<>(), new HashSet<>(),
errorMessagePart errorMessagePart
); );
SelectionCriteria criteria = SelectionCriteria.forMappingMethods( selectionParameters,
callingContextTargetPropertyName,
false
);
Assignment assignment = ctx.getMappingResolver().getTargetAssignment( Assignment assignment = ctx.getMappingResolver().getTargetAssignment(
method, method,
targetElementType, targetElementType,
callingContextTargetPropertyName,
formattingParameters, formattingParameters,
selectionParameters, criteria,
sourceRHS, sourceRHS,
false,
null null
); );
if ( assignment == null ) { if ( assignment == null && !criteria.hasQualfiers() ) {
assignment = forgeMapping( sourceRHS, sourceElementType, targetElementType ); assignment = forgeMapping( sourceRHS, sourceElementType, targetElementType );
if ( assignment != null ) { if ( assignment != null ) {
ctx.getMessager().note( 2, Message.ITERABLEMAPPING_CREATE_ELEMENT_NOTE, assignment ); ctx.getMessager().note( 2, Message.ITERABLEMAPPING_CREATE_ELEMENT_NOTE, assignment );

View File

@ -5,8 +5,6 @@
*/ */
package org.mapstruct.ap.internal.model; package org.mapstruct.ap.internal.model;
import static org.mapstruct.ap.internal.util.Collections.first;
import java.util.Collection; import java.util.Collection;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
@ -22,10 +20,13 @@ import org.mapstruct.ap.internal.model.common.Type;
import org.mapstruct.ap.internal.model.source.ForgedMethod; import org.mapstruct.ap.internal.model.source.ForgedMethod;
import org.mapstruct.ap.internal.model.source.Method; import org.mapstruct.ap.internal.model.source.Method;
import org.mapstruct.ap.internal.model.source.SelectionParameters; import org.mapstruct.ap.internal.model.source.SelectionParameters;
import org.mapstruct.ap.internal.model.source.selector.SelectionCriteria;
import org.mapstruct.ap.internal.prism.NullValueMappingStrategyPrism; import org.mapstruct.ap.internal.prism.NullValueMappingStrategyPrism;
import org.mapstruct.ap.internal.util.Message; import org.mapstruct.ap.internal.util.Message;
import org.mapstruct.ap.internal.util.Strings; import org.mapstruct.ap.internal.util.Strings;
import static org.mapstruct.ap.internal.util.Collections.first;
/** /**
* A {@link MappingMethod} implemented by a {@link Mapper} class which maps one {@code Map} type to another. Keys and * A {@link MappingMethod} implemented by a {@link Mapper} class which maps one {@code Map} type to another. Keys and
* values are mapped either by a {@link TypeConversion} or another mapping method if required. * values are mapped either by a {@link TypeConversion} or another mapping method if required.
@ -86,18 +87,20 @@ public class MapMappingMethod extends NormalTypeMappingMethod {
Type keyTargetType = resultTypeParams.get( 0 ).getTypeBound(); Type keyTargetType = resultTypeParams.get( 0 ).getTypeBound();
SourceRHS keySourceRHS = new SourceRHS( "entry.getKey()", keySourceType, new HashSet<>(), "map key" ); SourceRHS keySourceRHS = new SourceRHS( "entry.getKey()", keySourceType, new HashSet<>(), "map key" );
SelectionCriteria keyCriteria =
SelectionCriteria.forMappingMethods( keySelectionParameters, null, false );
Assignment keyAssignment = ctx.getMappingResolver().getTargetAssignment( Assignment keyAssignment = ctx.getMappingResolver().getTargetAssignment(
method, method,
keyTargetType, keyTargetType,
null, // there is no targetPropertyName
keyFormattingParameters, keyFormattingParameters,
keySelectionParameters, keyCriteria,
keySourceRHS, keySourceRHS,
false,
null null
); );
if ( keyAssignment == null ) { if ( keyAssignment == null && !keyCriteria.hasQualfiers( ) ) {
keyAssignment = forgeMapping( keySourceRHS, keySourceType, keyTargetType ); keyAssignment = forgeMapping( keySourceRHS, keySourceType, keyTargetType );
if ( keyAssignment != null ) { if ( keyAssignment != null ) {
ctx.getMessager().note( 2, Message.MAPMAPPING_CREATE_KEY_NOTE, keyAssignment ); ctx.getMessager().note( 2, Message.MAPMAPPING_CREATE_KEY_NOTE, keyAssignment );
@ -133,14 +136,16 @@ public class MapMappingMethod extends NormalTypeMappingMethod {
SourceRHS valueSourceRHS = new SourceRHS( "entry.getValue()", valueSourceType, new HashSet<>(), SourceRHS valueSourceRHS = new SourceRHS( "entry.getValue()", valueSourceType, new HashSet<>(),
"map value" ); "map value" );
SelectionCriteria valueCriteria =
SelectionCriteria.forMappingMethods( valueSelectionParameters, null, false );
Assignment valueAssignment = ctx.getMappingResolver().getTargetAssignment( Assignment valueAssignment = ctx.getMappingResolver().getTargetAssignment(
method, method,
valueTargetType, valueTargetType,
null, // there is no targetPropertyName
valueFormattingParameters, valueFormattingParameters,
valueSelectionParameters, valueCriteria,
valueSourceRHS, valueSourceRHS,
false,
null null
); );
@ -154,7 +159,7 @@ public class MapMappingMethod extends NormalTypeMappingMethod {
} }
} }
if ( valueAssignment == null ) { if ( valueAssignment == null && !valueCriteria.hasQualfiers( ) ) {
valueAssignment = forgeMapping( valueSourceRHS, valueSourceType, valueTargetType ); valueAssignment = forgeMapping( valueSourceRHS, valueSourceType, valueTargetType );
if ( valueAssignment != null ) { if ( valueAssignment != null ) {
ctx.getMessager().note( 2, Message.MAPMAPPING_CREATE_VALUE_NOTE, valueAssignment ); ctx.getMessager().note( 2, Message.MAPMAPPING_CREATE_VALUE_NOTE, valueAssignment );
@ -221,6 +226,7 @@ public class MapMappingMethod extends NormalTypeMappingMethod {
protected boolean shouldUsePropertyNamesInHistory() { protected boolean shouldUsePropertyNamesInHistory() {
return true; return true;
} }
} }
private MapMappingMethod(Method method, Collection<String> existingVariableNames, Assignment keyAssignment, private MapMappingMethod(Method method, Collection<String> existingVariableNames, Assignment keyAssignment,

View File

@ -7,6 +7,7 @@ package org.mapstruct.ap.internal.model;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
@ -23,8 +24,8 @@ import org.mapstruct.ap.internal.model.common.Type;
import org.mapstruct.ap.internal.model.common.TypeFactory; import org.mapstruct.ap.internal.model.common.TypeFactory;
import org.mapstruct.ap.internal.model.source.ForgedMethod; import org.mapstruct.ap.internal.model.source.ForgedMethod;
import org.mapstruct.ap.internal.model.source.Method; import org.mapstruct.ap.internal.model.source.Method;
import org.mapstruct.ap.internal.model.source.SelectionParameters;
import org.mapstruct.ap.internal.model.source.SourceMethod; import org.mapstruct.ap.internal.model.source.SourceMethod;
import org.mapstruct.ap.internal.model.source.selector.SelectionCriteria;
import org.mapstruct.ap.internal.option.Options; import org.mapstruct.ap.internal.option.Options;
import org.mapstruct.ap.internal.util.AccessorNamingUtils; import org.mapstruct.ap.internal.util.AccessorNamingUtils;
import org.mapstruct.ap.internal.util.FormattingMessager; import org.mapstruct.ap.internal.util.FormattingMessager;
@ -76,11 +77,10 @@ public class MappingBuilderContext {
* *
* @param mappingMethod target mapping method * @param mappingMethod target mapping method
* @param targetType return type to match * @param targetType return type to match
* @param targetPropertyName name of the target property
* @param formattingParameters used for formatting dates and numbers * @param formattingParameters used for formatting dates and numbers
* @param selectionParameters parameters used in the selection process * @param criteria parameters criteria in the selection process
* @param sourceRHS source information * @param sourceRHS source information
* @param preferUpdateMethods selection should prefer update methods when present. * @param positionHint the mirror for reporting problems
* *
* @return an assignment to a method parameter, which can either be: * @return an assignment to a method parameter, which can either be:
* <ol> * <ol>
@ -90,10 +90,10 @@ public class MappingBuilderContext {
* <li>null, no assignment found</li> * <li>null, no assignment found</li>
* </ol> * </ol>
*/ */
Assignment getTargetAssignment(Method mappingMethod, Type targetType, String targetPropertyName, Assignment getTargetAssignment(Method mappingMethod, Type targetType,
FormattingParameters formattingParameters, FormattingParameters formattingParameters,
SelectionParameters selectionParameters, SourceRHS sourceRHS, SelectionCriteria criteria, SourceRHS sourceRHS,
boolean preferUpdateMethods, AnnotationMirror mirror); AnnotationMirror positionHint);
Set<SupportingMappingMethod> getUsedSupportedMappings(); Set<SupportingMappingMethod> getUsedSupportedMappings();
} }
@ -191,12 +191,18 @@ public class MappingBuilderContext {
return mappingsToGenerate; return mappingsToGenerate;
} }
public List<String> getNamesOfMappingsToGenerate() { public List<String> getReservedNames() {
List<String> nameList = new ArrayList<>(); Set<String> nameSet = new HashSet<>();
for ( MappingMethod method : mappingsToGenerate ) { for ( MappingMethod method : mappingsToGenerate ) {
nameList.add( method.getName() ); nameSet.add( method.getName() );
} }
return nameList; // add existing names
for ( SourceMethod method : sourceModel) {
if ( method.isAbstract() ) {
nameSet.add( method.getName() );
}
}
return new ArrayList<>( nameSet );
} }
public MappingMethod getExistingMappingMethod(MappingMethod newMappingMethod) { public MappingMethod getExistingMappingMethod(MappingMethod newMappingMethod) {

View File

@ -36,6 +36,7 @@ import org.mapstruct.ap.internal.model.source.ParameterProvidedMethods;
import org.mapstruct.ap.internal.model.source.PropertyEntry; import org.mapstruct.ap.internal.model.source.PropertyEntry;
import org.mapstruct.ap.internal.model.source.SelectionParameters; import org.mapstruct.ap.internal.model.source.SelectionParameters;
import org.mapstruct.ap.internal.model.source.SourceReference; import org.mapstruct.ap.internal.model.source.SourceReference;
import org.mapstruct.ap.internal.model.source.selector.SelectionCriteria;
import org.mapstruct.ap.internal.prism.NullValueCheckStrategyPrism; import org.mapstruct.ap.internal.prism.NullValueCheckStrategyPrism;
import org.mapstruct.ap.internal.prism.NullValueMappingStrategyPrism; import org.mapstruct.ap.internal.prism.NullValueMappingStrategyPrism;
import org.mapstruct.ap.internal.prism.NullValuePropertyMappingStrategyPrism; import org.mapstruct.ap.internal.prism.NullValuePropertyMappingStrategyPrism;
@ -310,24 +311,27 @@ public class PropertyMapping extends ModelElement {
preferUpdateMethods = method.getMappingTargetParameter() != null; preferUpdateMethods = method.getMappingTargetParameter() != null;
} }
SelectionCriteria criteria = SelectionCriteria.forMappingMethods( selectionParameters,
targetPropertyName,
preferUpdateMethods
);
// forge a method instead of resolving one when there are mapping options. // forge a method instead of resolving one when there are mapping options.
Assignment assignment = null; Assignment assignment = null;
if ( forgeMethodWithMappingOptions == null ) { if ( forgeMethodWithMappingOptions == null ) {
assignment = ctx.getMappingResolver().getTargetAssignment( assignment = ctx.getMappingResolver().getTargetAssignment(
method, method,
targetType, targetType,
targetPropertyName,
formattingParameters, formattingParameters,
selectionParameters, criteria,
rightHandSide, rightHandSide,
preferUpdateMethods,
positionHint positionHint
); );
} }
Type sourceType = rightHandSide.getSourceType(); Type sourceType = rightHandSide.getSourceType();
// No mapping found. Try to forge a mapping // No mapping found. Try to forge a mapping
if ( assignment == null ) { if ( assignment == null && !criteria.hasQualfiers() ) {
if ( (sourceType.isCollectionType() || sourceType.isArrayType()) && targetType.isIterableType() ) { if ( (sourceType.isCollectionType() || sourceType.isArrayType()) && targetType.isIterableType() ) {
assignment = forgeIterableMapping( sourceType, targetType, rightHandSide, method.getExecutable() ); assignment = forgeIterableMapping( sourceType, targetType, rightHandSide, method.getExecutable() );
} }
@ -595,7 +599,7 @@ public class PropertyMapping extends ModelElement {
// forge a method from the parameter type to the last entry type. // forge a method from the parameter type to the last entry type.
String forgedName = Strings.joinAndCamelize( sourceReference.getElementNames() ); String forgedName = Strings.joinAndCamelize( sourceReference.getElementNames() );
forgedName = Strings.getSafeVariableName( forgedName, ctx.getNamesOfMappingsToGenerate() ); forgedName = Strings.getSafeVariableName( forgedName, ctx.getReservedNames() );
ForgedMethod methodRef = new ForgedMethod( ForgedMethod methodRef = new ForgedMethod(
forgedName, forgedName,
sourceReference.getParameter().getType(), sourceReference.getParameter().getType(),
@ -699,7 +703,7 @@ public class PropertyMapping extends ModelElement {
private ForgedMethod prepareForgedMethod(Type sourceType, Type targetType, SourceRHS source, private ForgedMethod prepareForgedMethod(Type sourceType, Type targetType, SourceRHS source,
ExecutableElement element, String suffix) { ExecutableElement element, String suffix) {
String name = getName( sourceType, targetType ); String name = getName( sourceType, targetType );
name = Strings.getSafeVariableName( name, ctx.getNamesOfMappingsToGenerate() ); name = Strings.getSafeVariableName( name, ctx.getReservedNames() );
// copy mapper configuration from the source method, its the same mapper // copy mapper configuration from the source method, its the same mapper
MapperConfiguration config = method.getMapperConfiguration(); MapperConfiguration config = method.getMapperConfiguration();
@ -751,7 +755,7 @@ public class PropertyMapping extends ModelElement {
} }
String name = getName( sourceType, targetType ); String name = getName( sourceType, targetType );
name = Strings.getSafeVariableName( name, ctx.getNamesOfMappingsToGenerate() ); name = Strings.getSafeVariableName( name, ctx.getReservedNames() );
List<Parameter> parameters = new ArrayList<>( method.getContextParameters() ); List<Parameter> parameters = new ArrayList<>( method.getContextParameters() );
Type returnType; Type returnType;
@ -873,16 +877,19 @@ public class PropertyMapping extends ModelElement {
} }
Type sourceType = ctx.getTypeFactory().getTypeForLiteral( baseForLiteral ); Type sourceType = ctx.getTypeFactory().getTypeForLiteral( baseForLiteral );
SelectionCriteria criteria = SelectionCriteria.forMappingMethods( selectionParameters,
targetPropertyName,
method.getMappingTargetParameter() != null
);
Assignment assignment = null; Assignment assignment = null;
if ( !targetType.isEnumType() ) { if ( !targetType.isEnumType() ) {
assignment = ctx.getMappingResolver().getTargetAssignment( assignment = ctx.getMappingResolver().getTargetAssignment(
method, method,
targetType, targetType,
targetPropertyName,
formattingParameters, formattingParameters,
selectionParameters, criteria,
new SourceRHS( constantExpression, sourceType, existingVariableNames, sourceErrorMessagePart ), new SourceRHS( constantExpression, sourceType, existingVariableNames, sourceErrorMessagePart ),
method.getMappingTargetParameter() != null,
positionHint positionHint
); );
} }

View File

@ -138,12 +138,7 @@ public class QualifierSelector implements MethodSelector {
matches.add( candidate ); matches.add( candidate );
} }
} }
if ( !matches.isEmpty() ) { return matches;
return matches;
}
else {
return methods;
}
} }
} }

View File

@ -90,6 +90,10 @@ public class SelectionCriteria {
this.preferUpdateMapping = preferUpdateMapping; this.preferUpdateMapping = preferUpdateMapping;
} }
public boolean hasQualfiers() {
return !qualifiedByNames.isEmpty() || !qualifiers.isEmpty();
}
public static SelectionCriteria forMappingMethods(SelectionParameters selectionParameters, public static SelectionCriteria forMappingMethods(SelectionParameters selectionParameters,
String targetPropertyName, boolean preferUpdateMapping) { String targetPropertyName, boolean preferUpdateMapping) {

View File

@ -37,7 +37,6 @@ import org.mapstruct.ap.internal.model.common.SourceRHS;
import org.mapstruct.ap.internal.model.common.Type; import org.mapstruct.ap.internal.model.common.Type;
import org.mapstruct.ap.internal.model.common.TypeFactory; import org.mapstruct.ap.internal.model.common.TypeFactory;
import org.mapstruct.ap.internal.model.source.Method; import org.mapstruct.ap.internal.model.source.Method;
import org.mapstruct.ap.internal.model.source.SelectionParameters;
import org.mapstruct.ap.internal.model.source.builtin.BuiltInMappingMethods; import org.mapstruct.ap.internal.model.source.builtin.BuiltInMappingMethods;
import org.mapstruct.ap.internal.model.source.builtin.BuiltInMethod; import org.mapstruct.ap.internal.model.source.builtin.BuiltInMethod;
import org.mapstruct.ap.internal.model.source.selector.MethodSelectors; import org.mapstruct.ap.internal.model.source.selector.MethodSelectors;
@ -95,12 +94,10 @@ public class MappingResolverImpl implements MappingResolver {
} }
@Override @Override
public Assignment getTargetAssignment(Method mappingMethod, Type targetType, String targetPropertyName, public Assignment getTargetAssignment(Method mappingMethod, Type targetType,
FormattingParameters formattingParameters, SelectionParameters selectionParameters, SourceRHS sourceRHS, FormattingParameters formattingParameters,
boolean preferUpdateMapping, AnnotationMirror positionHint) { SelectionCriteria criteria, SourceRHS sourceRHS,
AnnotationMirror positionHint) {
SelectionCriteria criteria =
SelectionCriteria.forMappingMethods( selectionParameters, targetPropertyName, preferUpdateMapping );
ResolvingAttempt attempt = new ResolvingAttempt( ResolvingAttempt attempt = new ResolvingAttempt(
sourceModel, sourceModel,
@ -182,12 +179,13 @@ public class MappingResolverImpl implements MappingResolver {
} }
// then direct assignable // then direct assignable
if ( sourceType.isAssignableTo( targetType ) || if ( !hasQualfiers() ) {
if ( sourceType.isAssignableTo( targetType ) ||
isAssignableThroughCollectionCopyConstructor( sourceType, targetType ) ) { isAssignableThroughCollectionCopyConstructor( sourceType, targetType ) ) {
Assignment simpleAssignment = sourceRHS; Assignment simpleAssignment = sourceRHS;
return simpleAssignment; return simpleAssignment;
}
} }
// At this point the SourceType will either // At this point the SourceType will either
// 1. be a String // 1. be a String
// 2. or when its a primitive / wrapped type and analysis successful equal to its TargetType. But in that // 2. or when its a primitive / wrapped type and analysis successful equal to its TargetType. But in that
@ -196,25 +194,29 @@ public class MappingResolverImpl implements MappingResolver {
// in NativeType is not successful. We don't want to go through type conversion, double mappings etc. // in NativeType is not successful. We don't want to go through type conversion, double mappings etc.
// with something that we already know to be wrong. // with something that we already know to be wrong.
if ( sourceType.isLiteral() if ( sourceType.isLiteral()
&& "java.lang.String".equals( sourceType.getFullyQualifiedName( ) ) && "java.lang.String".equals( sourceType.getFullyQualifiedName() )
&& targetType.isNative() ) { && targetType.isNative() ) {
return null; return null;
} }
// then type conversion // then type conversion
ConversionAssignment conversion = resolveViaConversion( sourceType, targetType ); if ( !hasQualfiers() ) {
if ( conversion != null ) { ConversionAssignment conversion = resolveViaConversion( sourceType, targetType );
conversion.reportMessageWhenNarrowing( messager, this ); if ( conversion != null ) {
conversion.getAssignment().setAssignment( sourceRHS ); conversion.reportMessageWhenNarrowing( messager, this );
return conversion.getAssignment(); conversion.getAssignment().setAssignment( sourceRHS );
return conversion.getAssignment();
}
} }
// check for a built-in method // check for a built-in method
Assignment builtInMethod = resolveViaBuiltInMethod( sourceType, targetType ); if (!hasQualfiers() ) {
if ( builtInMethod != null ) { Assignment builtInMethod = resolveViaBuiltInMethod( sourceType, targetType );
builtInMethod.setAssignment( sourceRHS ); if ( builtInMethod != null ) {
usedSupportedMappings.addAll( supportingMethodCandidates ); builtInMethod.setAssignment( sourceRHS );
return builtInMethod; usedSupportedMappings.addAll( supportingMethodCandidates );
return builtInMethod;
}
} }
// 2 step method, first: method(method(source)) // 2 step method, first: method(method(source))
@ -235,7 +237,7 @@ public class MappingResolverImpl implements MappingResolver {
selectionCriteria.setPreferUpdateMapping( false ); selectionCriteria.setPreferUpdateMapping( false );
// 2 step method, finally: conversion(method(source)) // 2 step method, finally: conversion(method(source))
conversion = resolveViaMethodAndConversion( sourceType, targetType ); ConversionAssignment conversion = resolveViaMethodAndConversion( sourceType, targetType );
if ( conversion != null ) { if ( conversion != null ) {
usedSupportedMappings.addAll( supportingMethodCandidates ); usedSupportedMappings.addAll( supportingMethodCandidates );
return conversion.getAssignment(); return conversion.getAssignment();
@ -245,6 +247,10 @@ public class MappingResolverImpl implements MappingResolver {
return null; return null;
} }
private boolean hasQualfiers() {
return selectionCriteria != null && selectionCriteria.hasQualfiers();
}
private ConversionAssignment resolveViaConversion(Type sourceType, Type targetType) { private ConversionAssignment resolveViaConversion(Type sourceType, Type targetType) {
ConversionProvider conversionProvider = conversions.getConversion( sourceType, targetType ); ConversionProvider conversionProvider = conversions.getConversion( sourceType, targetType );
@ -379,9 +385,9 @@ public class MappingResolverImpl implements MappingResolver {
* </ul> * </ul>
* then this method tries to resolve this combination and make a mapping methodY( conversionX ( parameter ) ) * then this method tries to resolve this combination and make a mapping methodY( conversionX ( parameter ) )
* *
* In stead of directly using a built in method candidate all the return types as 'B' of all available built-in * In stead of directly using a built in method candidate all the return types as 'B' of all available built-in
* methods are used to resolve a mapping (assignment) from result type to 'B'. If a match is found, an attempt * methods are used to resolve a mapping (assignment) from result type to 'B'. If a match is found, an attempt
* is done to find a matching type conversion. * is done to find a matching type conversion.
*/ */
private Assignment resolveViaConversionAndMethod(Type sourceType, Type targetType) { private Assignment resolveViaConversionAndMethod(Type sourceType, Type targetType) {
@ -503,7 +509,8 @@ public class MappingResolverImpl implements MappingResolver {
if ( candidates.size() > 1 ) { if ( candidates.size() > 1 ) {
if ( sourceRHS.getSourceErrorMessagePart() != null ) { if ( sourceRHS.getSourceErrorMessagePart() != null ) {
messager.printMessage( mappingMethod.getExecutable(), messager.printMessage(
mappingMethod.getExecutable(),
positionHint, positionHint,
Message.GENERAL_AMBIGIOUS_MAPPING_METHOD, Message.GENERAL_AMBIGIOUS_MAPPING_METHOD,
sourceRHS.getSourceErrorMessagePart(), sourceRHS.getSourceErrorMessagePart(),
@ -512,7 +519,8 @@ public class MappingResolverImpl implements MappingResolver {
); );
} }
else { else {
messager.printMessage( mappingMethod.getExecutable(), messager.printMessage(
mappingMethod.getExecutable(),
positionHint, positionHint,
Message.GENERAL_AMBIGIOUS_FACTORY_METHOD, Message.GENERAL_AMBIGIOUS_FACTORY_METHOD,
returnType, returnType,
@ -535,7 +543,8 @@ public class MappingResolverImpl implements MappingResolver {
return MethodReference.forMapperReference( return MethodReference.forMapperReference(
method.getMethod(), method.getMethod(),
mapperReference, mapperReference,
method.getParameterBindings() ); method.getParameterBindings()
);
} }
/** /**
@ -546,14 +555,14 @@ public class MappingResolverImpl implements MappingResolver {
boolean bothCollectionOrMap = false; boolean bothCollectionOrMap = false;
if ( ( sourceType.isCollectionType() && targetType.isCollectionType() ) || if ( ( sourceType.isCollectionType() && targetType.isCollectionType() ) ||
( sourceType.isMapType() && targetType.isMapType() ) ) { ( sourceType.isMapType() && targetType.isMapType() ) ) {
bothCollectionOrMap = true; bothCollectionOrMap = true;
} }
if ( bothCollectionOrMap ) { if ( bothCollectionOrMap ) {
return hasCompatibleCopyConstructor( return hasCompatibleCopyConstructor(
sourceType, sourceType,
targetType.getImplementationType() != null ? targetType.getImplementationType() : targetType targetType.getImplementationType() != null ? targetType.getImplementationType() : targetType
); );
} }
@ -565,8 +574,9 @@ public class MappingResolverImpl implements MappingResolver {
* *
* @param sourceType the source type * @param sourceType the source type
* @param targetType the target type * @param targetType the target type
*
* @return {@code true} if the target type has a constructor accepting the given source type, {@code false} * @return {@code true} if the target type has a constructor accepting the given source type, {@code false}
* otherwise. * otherwise.
*/ */
private boolean hasCompatibleCopyConstructor(Type sourceType, Type targetType) { private boolean hasCompatibleCopyConstructor(Type sourceType, Type targetType) {
if ( targetType.isPrimitive() ) { if ( targetType.isPrimitive() ) {
@ -584,7 +594,8 @@ public class MappingResolverImpl implements MappingResolver {
// get the constructor resolved against the type arguments of specific target type // get the constructor resolved against the type arguments of specific target type
ExecutableType typedConstructor = (ExecutableType) typeUtils.asMemberOf( ExecutableType typedConstructor = (ExecutableType) typeUtils.asMemberOf(
(DeclaredType) targetType.getTypeMirror(), (DeclaredType) targetType.getTypeMirror(),
constructor ); constructor
);
TypeMirror parameterType = Collections.first( typedConstructor.getParameterTypes() ); TypeMirror parameterType = Collections.first( typedConstructor.getParameterTypes() );
if ( parameterType.getKind() == TypeKind.DECLARED ) { if ( parameterType.getKind() == TypeKind.DECLARED ) {
@ -603,7 +614,8 @@ public class MappingResolverImpl implements MappingResolver {
} }
parameterType = typeUtils.getDeclaredType( parameterType = typeUtils.getDeclaredType(
(TypeElement) p.asElement(), (TypeElement) p.asElement(),
typeArguments.toArray( new TypeMirror[typeArguments.size()] ) ); typeArguments.toArray( new TypeMirror[typeArguments.size()] )
);
} }
if ( typeUtils.isAssignable( sourceType.getTypeMirror(), parameterType ) ) { if ( typeUtils.isAssignable( sourceType.getTypeMirror(), parameterType ) ) {

View File

@ -0,0 +1,60 @@
/*
* 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._1714;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Named;
import org.mapstruct.factory.Mappers;
@Mapper
public interface Issue1714Mapper {
Issue1714Mapper INSTANCE = Mappers.getMapper( Issue1714Mapper.class );
@Mapping(source = "programInstance", target = "seasonNumber", qualifiedByName = "getSeasonNumber")
OfferEntity map(OnDemand offerStatusDTO);
@Named("getTitle")
default String mapTitle(Program programInstance) {
return "dont care";
}
@Named("getSeasonNumber")
default Integer mapSeasonNumber(Program programInstance) {
return 1;
}
class OfferEntity {
private String seasonNumber;
public String getSeasonNumber() {
return seasonNumber;
}
public void setSeasonNumber(String seasonNumber) {
this.seasonNumber = seasonNumber;
}
}
class OnDemand {
private Program programInstance;
public Program getProgramInstance() {
return programInstance;
}
public void setProgramInstance(Program programInstance) {
this.programInstance = programInstance;
}
}
class Program {
}
}

View File

@ -0,0 +1,34 @@
/*
* 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._1714;
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;
@RunWith(AnnotationProcessorTestRunner.class)
@IssueKey("1714")
@WithClasses({
Issue1714Mapper.class
})
public class Issue1714Test {
@Test
public void codeShouldBeGeneratedCorrectly() {
Issue1714Mapper.OnDemand source = new Issue1714Mapper.OnDemand();
source.setProgramInstance( new Issue1714Mapper.Program() );
Issue1714Mapper.OfferEntity result = Issue1714Mapper.INSTANCE.map( source );
assertThat( result.getSeasonNumber() ).isEqualTo( "1" );
}
}

View File

@ -21,6 +21,9 @@ public abstract class BaseMapper {
public abstract Target sourceToTarget(Source source); public abstract Target sourceToTarget(Source source);
@Qualified
public abstract Target sourceToTargetQualified(Source source);
private static final List<Invocation> INVOCATIONS = new ArrayList<Invocation>(); private static final List<Invocation> INVOCATIONS = new ArrayList<Invocation>();
@BeforeMapping @BeforeMapping

View File

@ -96,14 +96,15 @@ public class QualifierTest {
ErroneousMapper.class ErroneousMapper.class
} ) } )
@ExpectedCompilationOutcome( @ExpectedCompilationOutcome(
value = CompilationResult.FAILED, value = CompilationResult.FAILED,
diagnostics = { diagnostics = {
@Diagnostic( type = ErroneousMapper.class, @Diagnostic(type = ErroneousMapper.class,
kind = Kind.ERROR, kind = Kind.ERROR,
line = 28, line = 28,
messageRegExp = "Ambiguous mapping methods found for mapping property " messageRegExp =
+ "\"java.lang.String title\" to java.lang.String.*" ) "Can't map property \"java.lang.String title\" to \"java.lang.String title\". "
} + "Consider to declare/implement a mapping method: \"java.lang.String map(java.lang.String value)*")
}
) )
public void shouldNotProduceMatchingMethod() { public void shouldNotProduceMatchingMethod() {
} }

View File

@ -9,6 +9,9 @@ import java.lang.annotation.ElementType;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
import java.util.List;
import org.mapstruct.IterableMapping;
import org.mapstruct.Mapper; import org.mapstruct.Mapper;
import org.mapstruct.Mapping; import org.mapstruct.Mapping;
import org.mapstruct.Qualifier; import org.mapstruct.Qualifier;
@ -29,6 +32,14 @@ public abstract class TopologyMapper {
@Mapping( target = "topologyFeatures", qualifiedBy = Cities.class ) @Mapping( target = "topologyFeatures", qualifiedBy = Cities.class )
public abstract TopologyEntity mapTopologyAsCity(TopologyDto dto); public abstract TopologyEntity mapTopologyAsCity(TopologyDto dto);
@Rivers
@IterableMapping( qualifiedBy = Rivers.class )
public abstract List<TopologyFeatureEntity> mapTopologiesAsRiver(List<TopologyFeatureDto> in);
@Cities
@IterableMapping( qualifiedBy = Cities.class )
public abstract List<TopologyFeatureEntity> mapTopologiesAsCities(List<TopologyFeatureDto> in);
@Rivers @Rivers
protected TopologyFeatureEntity mapRiver( TopologyFeatureDto dto ) { protected TopologyFeatureEntity mapRiver( TopologyFeatureDto dto ) {
TopologyFeatureEntity topologyFeatureEntity = null; TopologyFeatureEntity topologyFeatureEntity = null;