#3323 Support access to the source property name

This commit is contained in:
Oliver Erhart 2024-02-11 10:42:23 +01:00 committed by GitHub
parent 0a43bc088f
commit 8191c850e0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
40 changed files with 979 additions and 100 deletions

View File

@ -24,6 +24,7 @@ import java.lang.annotation.Target;
* <li>The mapping source parameter</li>
* <li>{@code @}{@link Context} parameter</li>
* <li>{@code @}{@link TargetPropertyName} parameter</li>
* <li>{@code @}{@link SourcePropertyName} parameter</li>
* </ul>
*
* <strong>Note:</strong> The usage of this annotation is <em>mandatory</em>

View File

@ -0,0 +1,26 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* This annotation marks a <em>presence check method</em> parameter as a source property name parameter.
* <p>
* This parameter enables conditional filtering based on source property name at run-time.
* Parameter must be of type {@link String} and can be present only in {@link Condition} method.
* </p>
*
* @author Oliver Erhart
* @since 1.6
*/
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.CLASS)
public @interface SourcePropertyName {
}

View File

@ -11,10 +11,10 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* This annotation marks a <em>presence check method</em> parameter as a property name parameter.
* This annotation marks a <em>presence check method</em> parameter as a target property name parameter.
* <p>
* This parameter enables conditional filtering based on target property name at run-time.
* Parameter must be of type {@link String} and can be present only in {@link Condition} method.
* This parameter enables conditional filtering based on target property name at run-time.
* Parameter must be of type {@link String} and can be present only in {@link Condition} method.
* </p>
* @author Nikola Ivačič
* @since 1.6

View File

@ -406,9 +406,9 @@ public class CarMapperImpl implements CarMapper {
----
====
Additionally `@TargetPropertyName` of type `java.lang.String` can be used in custom condition check method:
Additionally `@TargetPropertyName` or `@SourcePropertyName` of type `java.lang.String` can be used in custom condition check method:
.Mapper using custom condition check method with `@TargetPropertyName`
.Mapper using custom condition check method with `@TargetPropertyName` and `@SourcePropertyName`
====
[source, java, linenums]
[subs="verbatim,attributes"]
@ -416,11 +416,19 @@ Additionally `@TargetPropertyName` of type `java.lang.String` can be used in cus
@Mapper
public interface CarMapper {
@Mapping(target = "owner", source = "ownerName")
CarDto carToCarDto(Car car, @MappingTarget CarDto carDto);
@Condition
default boolean isNotEmpty(String value, @TargetPropertyName String name) {
if ( name.equals( "owner" ) {
default boolean isNotEmpty(
String value,
@TargetPropertyName String targetPropertyName,
@SourcePropertyName String sourcePropertyName
) {
if ( targetPropertyName.equals( "owner" )
&& sourcePropertyName.equals( "ownerName" ) ) {
return value != null
&& !value.isEmpty()
&& !value.equals( value.toLowerCase() );
@ -431,7 +439,7 @@ public interface CarMapper {
----
====
The generated mapper with `@TargetPropertyName` will look like:
The generated mapper with `@TargetPropertyName` and `@SourcePropertyName` will look like:
.Custom condition check in generated implementation
====
@ -447,7 +455,7 @@ public class CarMapperImpl implements CarMapper {
return carDto;
}
if ( isNotEmpty( car.getOwner(), "owner" ) ) {
if ( isNotEmpty( car.getOwner(), "owner", "ownerName" ) ) {
carDto.setOwner( car.getOwner() );
} else {
carDto.setOwner( null );
@ -470,7 +478,7 @@ If there is a custom `@Condition` method applicable for the property it will hav
====
Methods annotated with `@Condition` in addition to the value of the source property can also have the source parameter as an input.
`@TargetPropertyName` parameter can only be used in `@Condition` methods.
`@TargetPropertyName` and `@SourcePropertyName` parameters can only be used in `@Condition` methods.
====
<<selection-based-on-qualifiers>> is also valid for `@Condition` methods.

View File

@ -31,6 +31,7 @@ import org.mapstruct.Mappings;
import org.mapstruct.Named;
import org.mapstruct.ObjectFactory;
import org.mapstruct.Qualifier;
import org.mapstruct.SourcePropertyName;
import org.mapstruct.SubclassMapping;
import org.mapstruct.SubclassMappings;
import org.mapstruct.TargetPropertyName;
@ -57,6 +58,7 @@ import org.mapstruct.tools.gem.GemDefinition;
@GemDefinition(BeanMapping.class)
@GemDefinition(EnumMapping.class)
@GemDefinition(MapMapping.class)
@GemDefinition(SourcePropertyName.class)
@GemDefinition(SubclassMapping.class)
@GemDefinition(SubclassMappings.class)
@GemDefinition(TargetType.class)

View File

@ -1522,6 +1522,7 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
MappingReferences mappingRefs = extractMappingReferences( targetPropertyName, false );
PropertyMapping propertyMapping = new PropertyMappingBuilder().mappingContext( ctx )
.sourceMethod( method )
.sourcePropertyName( targetPropertyName )
.target( targetPropertyName, targetPropertyReadAccessor, targetPropertyWriteAccessor )
.sourceReference( sourceRef )
.existingVariableNames( existingVariableNames )

View File

@ -70,6 +70,7 @@ import static org.mapstruct.ap.internal.model.common.Assignment.AssignmentType.D
public class PropertyMapping extends ModelElement {
private final String name;
private final String sourcePropertyName;
private final String sourceBeanName;
private final String targetWriteAccessorName;
private final ReadAccessor targetReadAccessorProvider;
@ -286,6 +287,7 @@ public class PropertyMapping extends ModelElement {
}
return new PropertyMapping(
sourcePropertyName,
targetPropertyName,
rightHandSide.getSourceParameterName(),
targetWriteAccessor.getSimpleName(),
@ -1099,16 +1101,17 @@ public class PropertyMapping extends ModelElement {
ReadAccessor targetReadAccessorProvider,
Type targetType, Assignment propertyAssignment,
Set<String> dependsOn, Assignment defaultValueAssignment, boolean constructorMapping) {
this( name, null, targetWriteAccessorName, targetReadAccessorProvider,
this( name, null, null, targetWriteAccessorName, targetReadAccessorProvider,
targetType, propertyAssignment, dependsOn, defaultValueAssignment,
constructorMapping
);
}
private PropertyMapping(String name, String sourceBeanName, String targetWriteAccessorName,
ReadAccessor targetReadAccessorProvider, Type targetType,
Assignment assignment,
Set<String> dependsOn, Assignment defaultValueAssignment, boolean constructorMapping) {
private PropertyMapping(String sourcePropertyName, String name, String sourceBeanName,
String targetWriteAccessorName, ReadAccessor targetReadAccessorProvider, Type targetType,
Assignment assignment,
Set<String> dependsOn, Assignment defaultValueAssignment, boolean constructorMapping) {
this.sourcePropertyName = sourcePropertyName;
this.name = name;
this.sourceBeanName = sourceBeanName;
this.targetWriteAccessorName = targetWriteAccessorName;
@ -1128,6 +1131,10 @@ public class PropertyMapping extends ModelElement {
return name;
}
public String getSourcePropertyName() {
return sourcePropertyName;
}
public String getSourceBeanName() {
return sourceBeanName;
}

View File

@ -14,8 +14,9 @@ import javax.lang.model.element.VariableElement;
import org.mapstruct.ap.internal.gem.ContextGem;
import org.mapstruct.ap.internal.gem.MappingTargetGem;
import org.mapstruct.ap.internal.gem.TargetTypeGem;
import org.mapstruct.ap.internal.gem.SourcePropertyNameGem;
import org.mapstruct.ap.internal.gem.TargetPropertyNameGem;
import org.mapstruct.ap.internal.gem.TargetTypeGem;
import org.mapstruct.ap.internal.util.Collections;
/**
@ -32,6 +33,7 @@ public class Parameter extends ModelElement {
private final boolean mappingTarget;
private final boolean targetType;
private final boolean mappingContext;
private final boolean sourcePropertyName;
private final boolean targetPropertyName;
private final boolean varArgs;
@ -44,12 +46,13 @@ public class Parameter extends ModelElement {
this.mappingTarget = MappingTargetGem.instanceOn( element ) != null;
this.targetType = TargetTypeGem.instanceOn( element ) != null;
this.mappingContext = ContextGem.instanceOn( element ) != null;
this.sourcePropertyName = SourcePropertyNameGem.instanceOn( element ) != null;
this.targetPropertyName = TargetPropertyNameGem.instanceOn( element ) != null;
this.varArgs = varArgs;
}
private Parameter(String name, Type type, boolean mappingTarget, boolean targetType, boolean mappingContext,
boolean targetPropertyName,
boolean sourcePropertyName, boolean targetPropertyName,
boolean varArgs) {
this.element = null;
this.name = name;
@ -58,12 +61,13 @@ public class Parameter extends ModelElement {
this.mappingTarget = mappingTarget;
this.targetType = targetType;
this.mappingContext = mappingContext;
this.sourcePropertyName = sourcePropertyName;
this.targetPropertyName = targetPropertyName;
this.varArgs = varArgs;
}
public Parameter(String name, Type type) {
this( name, type, false, false, false, false, false );
this( name, type, false, false, false, false, false, false );
}
public Element getElement() {
@ -99,6 +103,7 @@ public class Parameter extends ModelElement {
return ( mappingTarget ? "@MappingTarget " : "" )
+ ( targetType ? "@TargetType " : "" )
+ ( mappingContext ? "@Context " : "" )
+ ( sourcePropertyName ? "@SourcePropertyName " : "" )
+ ( targetPropertyName ? "@TargetPropertyName " : "" )
+ "%s " + name;
}
@ -120,6 +125,10 @@ public class Parameter extends ModelElement {
return targetPropertyName;
}
public boolean isSourcePropertyName() {
return sourcePropertyName;
}
public boolean isVarArgs() {
return varArgs;
}
@ -165,6 +174,7 @@ public class Parameter extends ModelElement {
false,
false,
false,
false,
false
);
}
@ -206,6 +216,10 @@ public class Parameter extends ModelElement {
return parameters.stream().filter( Parameter::isTargetType ).findAny().orElse( null );
}
public static Parameter getSourcePropertyNameParameter(List<Parameter> parameters) {
return parameters.stream().filter( Parameter::isSourcePropertyName ).findAny().orElse( null );
}
public static Parameter getTargetPropertyNameParameter(List<Parameter> parameters) {
return parameters.stream().filter( Parameter::isTargetPropertyName ).findAny().orElse( null );
}
@ -214,6 +228,7 @@ public class Parameter extends ModelElement {
return !parameter.isMappingTarget() &&
!parameter.isTargetType() &&
!parameter.isMappingContext() &&
!parameter.isSourcePropertyName() &&
!parameter.isTargetPropertyName();
}

View File

@ -22,16 +22,19 @@ public class ParameterBinding {
private final boolean targetType;
private final boolean mappingTarget;
private final boolean mappingContext;
private final boolean sourcePropertyName;
private final boolean targetPropertyName;
private final SourceRHS sourceRHS;
private ParameterBinding(Type parameterType, String variableName, boolean mappingTarget, boolean targetType,
boolean mappingContext, boolean targetPropertyName, SourceRHS sourceRHS) {
boolean mappingContext, boolean sourcePropertyName, boolean targetPropertyName,
SourceRHS sourceRHS) {
this.type = parameterType;
this.variableName = variableName;
this.targetType = targetType;
this.mappingTarget = mappingTarget;
this.mappingContext = mappingContext;
this.sourcePropertyName = sourcePropertyName;
this.targetPropertyName = targetPropertyName;
this.sourceRHS = sourceRHS;
}
@ -64,11 +67,18 @@ public class ParameterBinding {
return mappingContext;
}
/**
* @return {@code true}, if the parameter being bound is a {@code @SourcePropertyName} parameter.
*/
public boolean isSourcePropertyName() {
return sourcePropertyName;
}
/**
* @return {@code true}, if the parameter being bound is a {@code @TargetPropertyName} parameter.
*/
public boolean isTargetPropertyName() {
return targetPropertyName;
return targetPropertyName;
}
/**
@ -108,6 +118,7 @@ public class ParameterBinding {
parameter.isMappingTarget(),
parameter.isTargetType(),
parameter.isMappingContext(),
parameter.isSourcePropertyName(),
parameter.isTargetPropertyName(),
null
);
@ -129,6 +140,7 @@ public class ParameterBinding {
false,
false,
false,
false,
null
);
}
@ -138,14 +150,21 @@ public class ParameterBinding {
* @return a parameter binding representing a target type parameter
*/
public static ParameterBinding forTargetTypeBinding(Type classTypeOf) {
return new ParameterBinding( classTypeOf, null, false, true, false, false, null );
return new ParameterBinding( classTypeOf, null, false, true, false, false, false, null );
}
/**
* @return a parameter binding representing a target property name parameter
*/
public static ParameterBinding forTargetPropertyNameBinding(Type classTypeOf) {
return new ParameterBinding( classTypeOf, null, false, false, false, true, null );
return new ParameterBinding( classTypeOf, null, false, false, false, false, true, null );
}
/**
* @return a parameter binding representing a source property name parameter
*/
public static ParameterBinding forSourcePropertyNameBinding(Type classTypeOf) {
return new ParameterBinding( classTypeOf, null, false, false, false, true, false, null );
}
/**
@ -153,7 +172,7 @@ public class ParameterBinding {
* @return a parameter binding representing a mapping target parameter
*/
public static ParameterBinding forMappingTargetBinding(Type resultType) {
return new ParameterBinding( resultType, null, true, false, false, false, null );
return new ParameterBinding( resultType, null, true, false, false, false, false, null );
}
/**
@ -161,10 +180,10 @@ public class ParameterBinding {
* @return a parameter binding representing a mapping source type
*/
public static ParameterBinding forSourceTypeBinding(Type sourceType) {
return new ParameterBinding( sourceType, null, false, false, false, false, null );
return new ParameterBinding( sourceType, null, false, false, false, false, false, null );
}
public static ParameterBinding fromSourceRHS(SourceRHS sourceRHS) {
return new ParameterBinding( sourceRHS.getSourceType(), null, false, false, false, false, sourceRHS );
return new ParameterBinding( sourceRHS.getSourceType(), null, false, false, false, false, false, sourceRHS );
}
}

View File

@ -47,6 +47,7 @@ public class SourceMethod implements Method {
private final List<Parameter> parameters;
private final Parameter mappingTargetParameter;
private final Parameter targetTypeParameter;
private final Parameter sourcePropertyNameParameter;
private final Parameter targetPropertyNameParameter;
private final boolean isObjectFactory;
private final boolean isPresenceCheck;
@ -249,6 +250,7 @@ public class SourceMethod implements Method {
this.mappingTargetParameter = Parameter.getMappingTargetParameter( parameters );
this.targetTypeParameter = Parameter.getTargetTypeParameter( parameters );
this.sourcePropertyNameParameter = Parameter.getSourcePropertyNameParameter( parameters );
this.targetPropertyNameParameter = Parameter.getTargetPropertyNameParameter( parameters );
this.hasObjectFactoryAnnotation = ObjectFactoryGem.instanceOn( executable ) != null;
this.isObjectFactory = determineIfIsObjectFactory();
@ -265,9 +267,10 @@ public class SourceMethod implements Method {
private boolean determineIfIsObjectFactory() {
boolean hasNoSourceParameters = getSourceParameters().isEmpty();
boolean hasNoMappingTargetParam = getMappingTargetParameter() == null;
boolean hasNoSourcePropertyNameParam = getSourcePropertyNameParameter() == null;
boolean hasNoTargetPropertyNameParam = getTargetPropertyNameParameter() == null;
return !isLifecycleCallbackMethod() && !returnType.isVoid()
&& hasNoMappingTargetParam && hasNoTargetPropertyNameParam
&& hasNoMappingTargetParam && hasNoSourcePropertyNameParam && hasNoTargetPropertyNameParam
&& ( hasObjectFactoryAnnotation || hasNoSourceParameters );
}
@ -382,6 +385,10 @@ public class SourceMethod implements Method {
return targetTypeParameter;
}
public Parameter getSourcePropertyNameParameter() {
return sourcePropertyNameParameter;
}
public Parameter getTargetPropertyNameParameter() {
return targetPropertyNameParameter;
}

View File

@ -171,6 +171,7 @@ public class SelectionContext {
if ( sourceRHS != null ) {
availableParams.addAll( ParameterBinding.fromParameters( method.getParameters() ) );
availableParams.add( ParameterBinding.fromSourceRHS( sourceRHS ) );
addSourcePropertyNameBindings( availableParams, sourceRHS.getSourceType(), typeFactory );
}
else {
availableParams.addAll( ParameterBinding.fromParameters( method.getParameters() ) );
@ -189,6 +190,7 @@ public class SelectionContext {
List<ParameterBinding> availableParams = new ArrayList<>();
availableParams.add( ParameterBinding.forSourceTypeBinding( sourceType ) );
addSourcePropertyNameBindings( availableParams, sourceType, typeFactory );
for ( Parameter param : mappingMethod.getParameters() ) {
if ( param.isMappingContext() ) {
@ -201,6 +203,22 @@ public class SelectionContext {
return availableParams;
}
private static void addSourcePropertyNameBindings(List<ParameterBinding> availableParams, Type sourceType,
TypeFactory typeFactory) {
boolean sourcePropertyNameAvailable = false;
for ( ParameterBinding pb : availableParams ) {
if ( pb.isSourcePropertyName() ) {
sourcePropertyNameAvailable = true;
break;
}
}
if ( !sourcePropertyNameAvailable ) {
availableParams.add( ParameterBinding.forSourcePropertyNameBinding( typeFactory.getType( String.class ) ) );
}
}
/**
* Adds default parameter bindings for the mapping-target and target-type if not already available.
*

View File

@ -215,6 +215,7 @@ public class TypeSelector implements MethodSelector {
if ( parameter.isTargetType() == candidate.isTargetType()
&& parameter.isMappingTarget() == candidate.isMappingTarget()
&& parameter.isMappingContext() == candidate.isMappingContext()
&& parameter.isSourcePropertyName() == candidate.isSourcePropertyName()
&& parameter.isTargetPropertyName() == candidate.isTargetPropertyName()) {
result.add( candidate );
}

View File

@ -26,6 +26,7 @@ import org.mapstruct.ap.internal.gem.MapMappingGem;
import org.mapstruct.ap.internal.gem.MappingGem;
import org.mapstruct.ap.internal.gem.MappingsGem;
import org.mapstruct.ap.internal.gem.ObjectFactoryGem;
import org.mapstruct.ap.internal.gem.SourcePropertyNameGem;
import org.mapstruct.ap.internal.gem.SubclassMappingGem;
import org.mapstruct.ap.internal.gem.SubclassMappingsGem;
import org.mapstruct.ap.internal.gem.TargetPropertyNameGem;
@ -415,6 +416,16 @@ public class MethodRetrievalProcessor implements ModelElementProcessor<Void, Lis
private boolean isValidPresenceCheckMethod(ExecutableElement method, List<Parameter> parameters, Type returnType) {
for ( Parameter param : parameters ) {
if ( param.isSourcePropertyName() && !param.getType().isString() ) {
messager.printMessage(
param.getElement(),
SourcePropertyNameGem.instanceOn( param.getElement() ).mirror(),
Message.RETRIEVAL_SOURCE_PROPERTY_NAME_WRONG_TYPE
);
return false;
}
if ( param.isTargetPropertyName() && !param.getType().isString() ) {
messager.printMessage(
param.getElement(),

View File

@ -177,6 +177,7 @@ public enum Message {
RETRIEVAL_MAPPER_USES_CYCLE( "The mapper %s is referenced itself in Mapper#uses.", Diagnostic.Kind.WARNING ),
RETRIEVAL_AFTER_METHOD_NOT_IMPLEMENTED( "@AfterMapping can only be applied to an implemented method." ),
RETRIEVAL_BEFORE_METHOD_NOT_IMPLEMENTED( "@BeforeMapping can only be applied to an implemented method." ),
RETRIEVAL_SOURCE_PROPERTY_NAME_WRONG_TYPE( "@SourcePropertyName can only by applied to a String parameter." ),
RETRIEVAL_TARGET_PROPERTY_NAME_WRONG_TYPE( "@TargetPropertyName can only by applied to a String parameter." ),
INHERITINVERSECONFIGURATION_DUPLICATES( "Several matching inverse methods exist: %s(). Specify a name explicitly." ),

View File

@ -44,6 +44,8 @@
<#if ext.targetBeanName??>${ext.targetBeanName}<#else>${param.variableName}</#if><#if ext.targetReadAccessorName??>.${ext.targetReadAccessorName}</#if><#t>
<#elseif param.mappingContext>
${param.variableName}<#t>
<#elseif param.sourcePropertyName>
"${ext.sourcePropertyName}"<#t>
<#elseif param.targetPropertyName>
"${ext.targetPropertyName}"<#t>
<#elseif param.sourceRHS??>
@ -60,7 +62,7 @@
</#macro>
<#--
macro: assignment
purpose: note: takes its targetyType from the singleSourceParameterType
purpose: note: takes its targetType from the singleSourceParameterType
-->
<#macro _assignment assignmentToUse>
<@includeModel object=assignmentToUse
@ -69,6 +71,7 @@
existingInstanceMapping=ext.existingInstanceMapping
targetReadAccessorName=ext.targetReadAccessorName
targetWriteAccessorName=ext.targetWriteAccessorName
sourcePropertyName=ext.sourcePropertyName
targetPropertyName=ext.targetPropertyName
targetType=singleSourceParameterType/>
</#macro>
</#macro>

View File

@ -8,5 +8,6 @@
<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.MethodReferencePresenceCheck" -->
<#if isNegate()>!</#if><@includeModel object=methodReference
presenceCheck=true
sourcePropertyName=ext.sourcePropertyName
targetPropertyName=ext.targetPropertyName
targetType=ext.targetType/>
targetType=ext.targetType/>

View File

@ -11,6 +11,7 @@
existingInstanceMapping=ext.existingInstanceMapping
targetReadAccessorName=targetReadAccessorName
targetWriteAccessorName=targetWriteAccessorName
sourcePropertyName=sourcePropertyName
targetPropertyName=name
targetType=targetType
defaultValueAssignment=defaultValueAssignment />

View File

@ -14,6 +14,7 @@ ${openExpression}<@_assignment/>${closeExpression}
existingInstanceMapping=ext.existingInstanceMapping
targetReadAccessorName=ext.targetReadAccessorName
targetWriteAccessorName=ext.targetWriteAccessorName
sourcePropertyName=ext.sourcePropertyName
targetPropertyName=ext.targetPropertyName
targetType=ext.targetType/>
</#macro>

View File

@ -17,6 +17,7 @@
<#if sourcePresenceCheckerReference??>
if ( <@includeModel object=sourcePresenceCheckerReference
targetPropertyName=ext.targetPropertyName
sourcePropertyName=ext.sourcePropertyName
targetType=ext.targetType/> ) {
<#nested>
}
@ -61,6 +62,7 @@
<#if sourcePresenceCheckerReference??>
if ( <@includeModel object=sourcePresenceCheckerReference
targetType=ext.targetType
sourcePropertyName=ext.sourcePropertyName
targetPropertyName=ext.targetPropertyName /> ) {
<#if needs_explicit_local_var>
<@includeModel object=nullCheckLocalVarType/> ${nullCheckLocalVarName} = <@lib.handleAssignment/>;

View File

@ -3,7 +3,7 @@
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.ap.test.conditional.targetpropertyname;
package org.mapstruct.ap.test.conditional.propertyname;
/**
* @author Nikola Ivačič

View File

@ -3,7 +3,7 @@
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.ap.test.conditional.targetpropertyname;
package org.mapstruct.ap.test.conditional.propertyname;
/**
* @author Nikola Ivačič

View File

@ -3,7 +3,7 @@
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.ap.test.conditional.targetpropertyname;
package org.mapstruct.ap.test.conditional.propertyname;
/**
* Target Property Name test entities

View File

@ -3,7 +3,7 @@
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.ap.test.conditional.targetpropertyname;
package org.mapstruct.ap.test.conditional.propertyname;
import java.util.List;

View File

@ -3,7 +3,7 @@
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.ap.test.conditional.targetpropertyname;
package org.mapstruct.ap.test.conditional.propertyname;
import java.util.List;
@ -15,14 +15,14 @@ public class EmployeeDto implements DomainModel {
private String firstName;
private String lastName;
private String title;
private String country;
private String originCountry;
private boolean active;
private int age;
private EmployeeDto boss;
private AddressDto primaryAddress;
private List<AddressDto> addresses;
private List<AddressDto> originAddresses;
public String getFirstName() {
return firstName;
@ -48,12 +48,12 @@ public class EmployeeDto implements DomainModel {
this.title = title;
}
public String getCountry() {
return country;
public String getOriginCountry() {
return originCountry;
}
public void setCountry(String country) {
this.country = country;
public void setOriginCountry(String originCountry) {
this.originCountry = originCountry;
}
public boolean isActive() {
@ -88,11 +88,11 @@ public class EmployeeDto implements DomainModel {
this.primaryAddress = primaryAddress;
}
public List<AddressDto> getAddresses() {
return addresses;
public List<AddressDto> getOriginAddresses() {
return originAddresses;
}
public void setAddresses(List<AddressDto> addresses) {
this.addresses = addresses;
public void setOriginAddresses(List<AddressDto> originAddresses) {
this.originAddresses = originAddresses;
}
}

View File

@ -0,0 +1,48 @@
/*
* 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.conditional.propertyname.sourcepropertyname;
import java.util.Collection;
import org.mapstruct.Condition;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.SourcePropertyName;
import org.mapstruct.ap.test.conditional.propertyname.Employee;
import org.mapstruct.ap.test.conditional.propertyname.EmployeeDto;
import org.mapstruct.factory.Mappers;
/**
* @author Nikola Ivačič
* @author Mohammad Al Zouabi
* @author Oliver Erhart
*/
@Mapper
public interface ConditionalMethodForCollectionMapperWithSourcePropertyName {
ConditionalMethodForCollectionMapperWithSourcePropertyName INSTANCE
= Mappers.getMapper( ConditionalMethodForCollectionMapperWithSourcePropertyName.class );
@Mapping(target = "country", source = "originCountry")
@Mapping(target = "addresses", source = "originAddresses")
Employee map(EmployeeDto employee);
@Condition
default <T> boolean isNotEmpty(Collection<T> collection, @SourcePropertyName String propName) {
if ( "originAddresses".equalsIgnoreCase( propName ) ) {
return false;
}
return collection != null && !collection.isEmpty();
}
@Condition
default boolean isNotBlank(String value, @SourcePropertyName String propName) {
if ( propName.equalsIgnoreCase( "originCountry" ) ) {
return false;
}
return value != null && !value.trim().isEmpty();
}
}

View File

@ -0,0 +1,54 @@
/*
* 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.conditional.propertyname.sourcepropertyname;
import java.util.LinkedHashSet;
import java.util.Set;
import org.mapstruct.Condition;
import org.mapstruct.Context;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.SourcePropertyName;
import org.mapstruct.ap.test.conditional.propertyname.DomainModel;
import org.mapstruct.ap.test.conditional.propertyname.Employee;
import org.mapstruct.ap.test.conditional.propertyname.EmployeeDto;
import org.mapstruct.factory.Mappers;
/**
* @author Filip Hrisafov
* @author Nikola Ivačič
* @author Mohammad Al Zouabi
* @author Oliver Erhart
*/
@Mapper
public interface ConditionalMethodInMapperWithAllExceptTarget {
ConditionalMethodInMapperWithAllExceptTarget INSTANCE
= Mappers.getMapper( ConditionalMethodInMapperWithAllExceptTarget.class );
class PresenceUtils {
Set<String> visited = new LinkedHashSet<>();
Set<String> visitedSources = new LinkedHashSet<>();
}
@Mapping(target = "country", source = "originCountry")
@Mapping(target = "addresses", source = "originAddresses")
Employee map(EmployeeDto employee, @Context PresenceUtils utils);
@Condition
default boolean isNotBlank(String value,
DomainModel source,
@SourcePropertyName String propName,
@Context PresenceUtils utils) {
utils.visited.add( propName );
utils.visitedSources.add( source.getClass().getSimpleName() );
if ( propName.equalsIgnoreCase( "firstName" ) ) {
return true;
}
return value != null && !value.trim().isEmpty();
}
}

View File

@ -0,0 +1,64 @@
/*
* 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.conditional.propertyname.sourcepropertyname;
import java.util.LinkedHashSet;
import java.util.Set;
import org.mapstruct.Condition;
import org.mapstruct.Context;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.MappingTarget;
import org.mapstruct.SourcePropertyName;
import org.mapstruct.TargetPropertyName;
import org.mapstruct.ap.test.conditional.propertyname.DomainModel;
import org.mapstruct.ap.test.conditional.propertyname.Employee;
import org.mapstruct.ap.test.conditional.propertyname.EmployeeDto;
import org.mapstruct.factory.Mappers;
/**
* @author Filip Hrisafov
* @author Nikola Ivačič
* @author Mohammad Al Zouabi
* @author Oliver Erhart
*/
@Mapper
public interface ConditionalMethodInMapperWithAllOptions {
ConditionalMethodInMapperWithAllOptions INSTANCE
= Mappers.getMapper( ConditionalMethodInMapperWithAllOptions.class );
class PresenceUtils {
Set<String> visitedSourceNames = new LinkedHashSet<>();
Set<String> visitedTargetNames = new LinkedHashSet<>();
Set<String> visitedSources = new LinkedHashSet<>();
Set<String> visitedTargets = new LinkedHashSet<>();
}
@Mapping(target = "country", source = "originCountry")
@Mapping(target = "addresses", source = "originAddresses")
void map(EmployeeDto employeeDto,
@MappingTarget Employee employee,
@Context PresenceUtils utils);
@Condition
default boolean isNotBlank(String value,
DomainModel source,
@MappingTarget DomainModel target,
@SourcePropertyName String sourcePropName,
@TargetPropertyName String targetPropName,
@Context PresenceUtils utils) {
utils.visitedSourceNames.add( sourcePropName );
utils.visitedTargetNames.add( targetPropName );
utils.visitedSources.add( source.getClass().getSimpleName() );
utils.visitedTargets.add( target.getClass().getSimpleName() );
if ( sourcePropName.equalsIgnoreCase( "lastName" ) ) {
return false;
}
return value != null && !value.trim().isEmpty();
}
}

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.conditional.propertyname.sourcepropertyname;
import org.mapstruct.Condition;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.SourcePropertyName;
import org.mapstruct.ap.test.conditional.propertyname.Employee;
import org.mapstruct.ap.test.conditional.propertyname.EmployeeDto;
import org.mapstruct.factory.Mappers;
/**
* @author Filip Hrisafov
* @author Nikola Ivačič
* @author Mohammad Al Zouabi
* @author Oliver Erhart
*/
@Mapper
public interface ConditionalMethodInMapperWithSourcePropertyName {
ConditionalMethodInMapperWithSourcePropertyName INSTANCE
= Mappers.getMapper( ConditionalMethodInMapperWithSourcePropertyName.class );
@Mapping(target = "country", source = "originCountry")
@Mapping(target = "addresses", source = "originAddresses")
Employee map(EmployeeDto employee);
@Condition
default boolean isNotBlank(String value, @SourcePropertyName String propName) {
if ( propName.equalsIgnoreCase( "originCountry" ) ) {
return false;
}
return value != null && !value.trim().isEmpty();
}
}

View File

@ -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.conditional.propertyname.sourcepropertyname;
import org.mapstruct.Condition;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.SourcePropertyName;
import org.mapstruct.ap.test.conditional.propertyname.Employee;
import org.mapstruct.ap.test.conditional.propertyname.EmployeeDto;
import org.mapstruct.factory.Mappers;
/**
* @author Filip Hrisafov
* @author Nikola Ivačič
* @author Mohammad Al Zouabi
* @author Oliver Erhart
*/
@Mapper(uses = ConditionalMethodInUsesMapperWithSourcePropertyName.PresenceUtils.class)
public interface ConditionalMethodInUsesMapperWithSourcePropertyName {
ConditionalMethodInUsesMapperWithSourcePropertyName INSTANCE
= Mappers.getMapper( ConditionalMethodInUsesMapperWithSourcePropertyName.class );
@Mapping(target = "country", source = "originCountry")
@Mapping(target = "addresses", source = "originAddresses")
Employee map(EmployeeDto employee);
class PresenceUtils {
@Condition
public boolean isNotBlank(String value, @SourcePropertyName String propName) {
if ( propName.equalsIgnoreCase( "originCountry" ) ) {
return false;
}
return value != null && !value.trim().isEmpty();
}
}
}

View File

@ -0,0 +1,108 @@
/*
* 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.conditional.propertyname.sourcepropertyname;
import java.util.Deque;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import org.mapstruct.AfterMapping;
import org.mapstruct.BeforeMapping;
import org.mapstruct.Condition;
import org.mapstruct.Context;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.SourcePropertyName;
import org.mapstruct.TargetType;
import org.mapstruct.ap.test.conditional.propertyname.Address;
import org.mapstruct.ap.test.conditional.propertyname.AddressDto;
import org.mapstruct.ap.test.conditional.propertyname.DomainModel;
import org.mapstruct.ap.test.conditional.propertyname.Employee;
import org.mapstruct.ap.test.conditional.propertyname.EmployeeDto;
import org.mapstruct.factory.Mappers;
/**
* @author Nikola Ivačič
* @author Mohammad Al Zouabi
* @author Oliver Erhart
*/
@Mapper
public interface ConditionalMethodWithSourcePropertyNameInContextMapper {
ConditionalMethodWithSourcePropertyNameInContextMapper INSTANCE
= Mappers.getMapper( ConditionalMethodWithSourcePropertyNameInContextMapper.class );
@Mapping(target = "country", source = "originCountry")
@Mapping(target = "addresses", source = "originAddresses")
Employee map(EmployeeDto employee, @Context PresenceUtils utils);
Address map(AddressDto addressDto, @Context PresenceUtils utils);
class PresenceUtils {
Set<String> visited = new LinkedHashSet<>();
@Condition
public boolean isNotBlank(String value, @SourcePropertyName String propName) {
visited.add( propName );
return value != null && !value.trim().isEmpty();
}
}
@Mapping(target = "country", source = "originCountry")
@Mapping(target = "addresses", source = "originAddresses")
Employee map(EmployeeDto employee, @Context PresenceUtilsAllProps utils);
Address map(AddressDto addressDto, @Context PresenceUtilsAllProps utils);
class PresenceUtilsAllProps {
Set<String> visited = new LinkedHashSet<>();
@Condition
public boolean collect(@SourcePropertyName String propName) {
visited.add( propName );
return true;
}
}
@Mapping(target = "country", source = "originCountry")
@Mapping(target = "addresses", source = "originAddresses")
Employee map(EmployeeDto employee, @Context PresenceUtilsAllPropsWithSource utils);
Address map(AddressDto addressDto, @Context PresenceUtilsAllPropsWithSource utils);
@BeforeMapping
default void before(DomainModel source, @Context PresenceUtilsAllPropsWithSource utils) {
String lastProp = utils.visitedSegments.peekLast();
if ( lastProp != null && source != null ) {
utils.path.offerLast( lastProp );
}
}
@AfterMapping
default <T> void after(@TargetType Class<T> targetClass, @Context PresenceUtilsAllPropsWithSource utils) {
// intermediate method for collection mapping must not change the path
if (targetClass != List.class) {
utils.path.pollLast();
}
}
class PresenceUtilsAllPropsWithSource {
Deque<String> visitedSegments = new LinkedList<>();
Deque<String> visited = new LinkedList<>();
Deque<String> path = new LinkedList<>();
@Condition
public boolean collect(@SourcePropertyName String propName) {
visitedSegments.offerLast( propName );
path.offerLast( propName );
visited.offerLast( String.join( ".", path ) );
path.pollLast();
return true;
}
}
}

View File

@ -0,0 +1,26 @@
/*
* 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.conditional.propertyname.sourcepropertyname;
import org.mapstruct.Condition;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.SourcePropertyName;
import org.mapstruct.ap.test.conditional.propertyname.Employee;
import org.mapstruct.ap.test.conditional.propertyname.EmployeeDto;
@Mapper
public interface ErroneousNonStringSourcePropertyNameParameter {
@Mapping(target = "country", source = "originCountry")
@Mapping(target = "addresses", source = "originAddresses")
Employee map(EmployeeDto employee);
@Condition
default boolean isNotBlank(String value, @SourcePropertyName int propName) {
return value != null && !value.trim().isEmpty();
}
}

View File

@ -0,0 +1,312 @@
/*
* 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.conditional.propertyname.sourcepropertyname;
import java.util.Collections;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.mapstruct.ap.test.conditional.propertyname.Address;
import org.mapstruct.ap.test.conditional.propertyname.AddressDto;
import org.mapstruct.ap.test.conditional.propertyname.DomainModel;
import org.mapstruct.ap.test.conditional.propertyname.Employee;
import org.mapstruct.ap.test.conditional.propertyname.EmployeeDto;
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;
import org.mapstruct.ap.testutil.runner.GeneratedSource;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Filip Hrisafov
* @author Nikola Ivačič
* @author Mohammad Al Zouabi
* @author Oliver Erhart
*/
@IssueKey("3323")
@WithClasses({
Address.class,
AddressDto.class,
Employee.class,
EmployeeDto.class,
DomainModel.class
})
public class SourcePropertyNameTest {
@RegisterExtension
final GeneratedSource generatedSource = new GeneratedSource();
@ProcessorTest
@WithClasses({
ConditionalMethodInMapperWithSourcePropertyName.class
})
public void conditionalMethodInMapperWithSourcePropertyName() {
ConditionalMethodInMapperWithSourcePropertyName mapper
= ConditionalMethodInMapperWithSourcePropertyName.INSTANCE;
EmployeeDto employeeDto = new EmployeeDto();
employeeDto.setFirstName( " " );
employeeDto.setLastName( "Testirovich" );
employeeDto.setOriginCountry( "US" );
employeeDto.setOriginAddresses(
Collections.singletonList( new AddressDto( "Testing St. 6" ) )
);
Employee employee = mapper.map( employeeDto );
assertThat( employee.getLastName() ).isEqualTo( "Testirovich" );
assertThat( employee.getFirstName() ).isNull();
assertThat( employee.getCountry() ).isNull();
assertThat( employee.getAddresses() )
.extracting( Address::getStreet )
.containsExactly( "Testing St. 6" );
}
@ProcessorTest
@WithClasses({
ConditionalMethodForCollectionMapperWithSourcePropertyName.class
})
public void conditionalMethodForCollectionMapperWithSourcePropertyName() {
ConditionalMethodForCollectionMapperWithSourcePropertyName mapper
= ConditionalMethodForCollectionMapperWithSourcePropertyName.INSTANCE;
EmployeeDto employeeDto = new EmployeeDto();
employeeDto.setFirstName( " " );
employeeDto.setLastName( "Testirovich" );
employeeDto.setOriginCountry( "US" );
employeeDto.setOriginAddresses(
Collections.singletonList( new AddressDto( "Testing St. 6" ) )
);
Employee employee = mapper.map( employeeDto );
assertThat( employee.getLastName() ).isEqualTo( "Testirovich" );
assertThat( employee.getFirstName() ).isNull();
assertThat( employee.getCountry() ).isNull();
assertThat( employee.getAddresses() ).isNull();
}
@ProcessorTest
@WithClasses({
ConditionalMethodInUsesMapperWithSourcePropertyName.class
})
public void conditionalMethodInUsesMapperWithSourcePropertyName() {
ConditionalMethodInUsesMapperWithSourcePropertyName mapper
= ConditionalMethodInUsesMapperWithSourcePropertyName.INSTANCE;
EmployeeDto employeeDto = new EmployeeDto();
employeeDto.setFirstName( " " );
employeeDto.setLastName( "Testirovich" );
employeeDto.setOriginCountry( "US" );
employeeDto.setOriginAddresses(
Collections.singletonList( new AddressDto( "Testing St. 6" ) )
);
Employee employee = mapper.map( employeeDto );
assertThat( employee.getLastName() ).isEqualTo( "Testirovich" );
assertThat( employee.getFirstName() ).isNull();
assertThat( employee.getCountry() ).isNull();
assertThat( employee.getAddresses() )
.extracting( Address::getStreet )
.containsExactly( "Testing St. 6" );
}
@ProcessorTest
@WithClasses({
ConditionalMethodInMapperWithAllOptions.class
})
public void conditionalMethodInMapperWithAllOptions() {
ConditionalMethodInMapperWithAllOptions mapper
= ConditionalMethodInMapperWithAllOptions.INSTANCE;
ConditionalMethodInMapperWithAllOptions.PresenceUtils utils =
new ConditionalMethodInMapperWithAllOptions.PresenceUtils();
EmployeeDto employeeDto = new EmployeeDto();
employeeDto.setFirstName( " " );
employeeDto.setLastName( "Testirovich" );
employeeDto.setOriginCountry( "US" );
employeeDto.setOriginAddresses(
Collections.singletonList( new AddressDto( "Testing St. 6" ) )
);
Employee employee = new Employee();
mapper.map( employeeDto, employee, utils );
assertThat( employee.getLastName() ).isNull();
assertThat( employee.getFirstName() ).isNull();
assertThat( employee.getCountry() ).isEqualTo( "US" );
assertThat( employee.getAddresses() )
.extracting( Address::getStreet )
.containsExactly( "Testing St. 6" );
assertThat( utils.visitedSourceNames )
.containsExactlyInAnyOrder( "firstName", "lastName", "title", "originCountry" );
assertThat( utils.visitedTargetNames )
.containsExactlyInAnyOrder( "firstName", "lastName", "title", "country" );
assertThat( utils.visitedSources ).containsExactly( "EmployeeDto" );
assertThat( utils.visitedTargets ).containsExactly( "Employee" );
}
@ProcessorTest
@WithClasses({
ConditionalMethodInMapperWithAllExceptTarget.class
})
public void conditionalMethodInMapperWithAllExceptTarget() {
ConditionalMethodInMapperWithAllExceptTarget mapper
= ConditionalMethodInMapperWithAllExceptTarget.INSTANCE;
ConditionalMethodInMapperWithAllExceptTarget.PresenceUtils utils =
new ConditionalMethodInMapperWithAllExceptTarget.PresenceUtils();
EmployeeDto employeeDto = new EmployeeDto();
employeeDto.setFirstName( " " );
employeeDto.setLastName( "Testirovich" );
employeeDto.setOriginCountry( "US" );
employeeDto.setOriginAddresses(
Collections.singletonList( new AddressDto( "Testing St. 6" ) )
);
Employee employee = mapper.map( employeeDto, utils );
assertThat( employee.getLastName() ).isEqualTo( "Testirovich" );
assertThat( employee.getFirstName() ).isEqualTo( " " );
assertThat( employee.getCountry() ).isEqualTo( "US" );
assertThat( employee.getAddresses() )
.extracting( Address::getStreet )
.containsExactly( "Testing St. 6" );
assertThat( utils.visited )
.containsExactlyInAnyOrder( "firstName", "lastName", "title", "originCountry", "street" );
assertThat( utils.visitedSources ).containsExactlyInAnyOrder( "EmployeeDto", "AddressDto" );
}
@ProcessorTest
@WithClasses({
ConditionalMethodWithSourcePropertyNameInContextMapper.class
})
public void conditionalMethodWithSourcePropertyNameInUsesContextMapper() {
ConditionalMethodWithSourcePropertyNameInContextMapper mapper
= ConditionalMethodWithSourcePropertyNameInContextMapper.INSTANCE;
ConditionalMethodWithSourcePropertyNameInContextMapper.PresenceUtils utils =
new ConditionalMethodWithSourcePropertyNameInContextMapper.PresenceUtils();
EmployeeDto employeeDto = new EmployeeDto();
employeeDto.setLastName( " " );
employeeDto.setOriginCountry( "US" );
employeeDto.setOriginAddresses(
Collections.singletonList( new AddressDto( "Testing St. 6" ) )
);
Employee employee = mapper.map( employeeDto, utils );
assertThat( employee.getLastName() ).isNull();
assertThat( employee.getCountry() ).isEqualTo( "US" );
assertThat( employee.getAddresses() )
.extracting( Address::getStreet )
.containsExactly( "Testing St. 6" );
assertThat( utils.visited )
.containsExactlyInAnyOrder( "firstName", "lastName", "title", "originCountry", "street" );
ConditionalMethodWithSourcePropertyNameInContextMapper.PresenceUtilsAllProps allPropsUtils =
new ConditionalMethodWithSourcePropertyNameInContextMapper.PresenceUtilsAllProps();
employeeDto = new EmployeeDto();
employeeDto.setLastName( "Tester" );
employeeDto.setOriginCountry( "US" );
employeeDto.setOriginAddresses(
Collections.singletonList( new AddressDto( "Testing St. 6" ) )
);
employee = mapper.map( employeeDto, allPropsUtils );
assertThat( employee.getLastName() ).isEqualTo( "Tester" );
assertThat( employee.getCountry() ).isEqualTo( "US" );
assertThat( employee.getAddresses() )
.extracting( Address::getStreet )
.containsExactly( "Testing St. 6" );
assertThat( allPropsUtils.visited )
.containsExactlyInAnyOrder(
"firstName",
"lastName",
"title",
"originCountry",
"active",
"age",
"boss",
"primaryAddress",
"originAddresses",
"street"
);
ConditionalMethodWithSourcePropertyNameInContextMapper.PresenceUtilsAllPropsWithSource allPropsUtilsWithSource =
new ConditionalMethodWithSourcePropertyNameInContextMapper.PresenceUtilsAllPropsWithSource();
EmployeeDto bossEmployeeDto = new EmployeeDto();
bossEmployeeDto.setLastName( "Boss Tester" );
bossEmployeeDto.setOriginCountry( "US" );
bossEmployeeDto.setOriginAddresses( Collections.singletonList( new AddressDto(
"Testing St. 10" ) ) );
employeeDto = new EmployeeDto();
employeeDto.setLastName( "Tester" );
employeeDto.setOriginCountry( "US" );
employeeDto.setBoss( bossEmployeeDto );
employeeDto.setOriginAddresses(
Collections.singletonList( new AddressDto( "Testing St. 6" ) )
);
employee = mapper.map( employeeDto, allPropsUtilsWithSource );
assertThat( employee.getLastName() ).isEqualTo( "Tester" );
assertThat( employee.getCountry() ).isEqualTo( "US" );
assertThat( employee.getAddresses() ).isNotEmpty();
assertThat( employee.getAddresses().get( 0 ).getStreet() ).isEqualTo( "Testing St. 6" );
assertThat( employee.getBoss() ).isNotNull();
assertThat( employee.getBoss().getCountry() ).isEqualTo( "US" );
assertThat( employee.getBoss().getLastName() ).isEqualTo( "Boss Tester" );
assertThat( employee.getBoss().getAddresses() )
.extracting( Address::getStreet )
.containsExactly( "Testing St. 10" );
assertThat( allPropsUtilsWithSource.visited )
.containsExactly(
"originCountry",
"originAddresses",
"originAddresses.street",
"firstName",
"lastName",
"title",
"active",
"age",
"boss",
"boss.originCountry",
"boss.originAddresses",
"boss.originAddresses.street",
"boss.firstName",
"boss.lastName",
"boss.title",
"boss.active",
"boss.age",
"boss.boss",
"boss.primaryAddress",
"primaryAddress"
);
}
@ProcessorTest
@WithClasses({
ErroneousNonStringSourcePropertyNameParameter.class
})
@ExpectedCompilationOutcome(
value = CompilationResult.FAILED,
diagnostics = {
@Diagnostic(
kind = javax.tools.Diagnostic.Kind.ERROR,
type = ErroneousNonStringSourcePropertyNameParameter.class,
line = 23,
message = "@SourcePropertyName can only by applied to a String parameter."
)
}
)
public void nonStringSourcePropertyNameParameter() {
}
}

View File

@ -3,15 +3,18 @@
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.ap.test.conditional.targetpropertyname;
package org.mapstruct.ap.test.conditional.propertyname.targetpropertyname;
import java.util.Collection;
import org.mapstruct.Condition;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.TargetPropertyName;
import org.mapstruct.ap.test.conditional.propertyname.Employee;
import org.mapstruct.ap.test.conditional.propertyname.EmployeeDto;
import org.mapstruct.factory.Mappers;
import java.util.Collection;
/**
* @author Nikola Ivačič
*/
@ -21,6 +24,8 @@ public interface ConditionalMethodForCollectionMapperWithTargetPropertyName {
ConditionalMethodForCollectionMapperWithTargetPropertyName INSTANCE
= Mappers.getMapper( ConditionalMethodForCollectionMapperWithTargetPropertyName.class );
@Mapping(target = "country", source = "originCountry")
@Mapping(target = "addresses", source = "originAddresses")
Employee map(EmployeeDto employee);
@Condition

View File

@ -3,17 +3,21 @@
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.ap.test.conditional.targetpropertyname;
package org.mapstruct.ap.test.conditional.propertyname.targetpropertyname;
import java.util.LinkedHashSet;
import java.util.Set;
import org.mapstruct.Condition;
import org.mapstruct.Context;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.TargetPropertyName;
import org.mapstruct.ap.test.conditional.propertyname.DomainModel;
import org.mapstruct.ap.test.conditional.propertyname.Employee;
import org.mapstruct.ap.test.conditional.propertyname.EmployeeDto;
import org.mapstruct.factory.Mappers;
import java.util.LinkedHashSet;
import java.util.Set;
/**
* @author Filip Hrisafov
* @author Nikola Ivačič
@ -29,6 +33,8 @@ public interface ConditionalMethodInMapperWithAllExceptTarget {
Set<String> visitedSources = new LinkedHashSet<>();
}
@Mapping(target = "country", source = "originCountry")
@Mapping(target = "addresses", source = "originAddresses")
Employee map(EmployeeDto employee, @Context PresenceUtils utils);
@Condition

View File

@ -3,18 +3,23 @@
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.ap.test.conditional.targetpropertyname;
package org.mapstruct.ap.test.conditional.propertyname.targetpropertyname;
import java.util.LinkedHashSet;
import java.util.Set;
import org.mapstruct.Condition;
import org.mapstruct.Context;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.MappingTarget;
import org.mapstruct.SourcePropertyName;
import org.mapstruct.TargetPropertyName;
import org.mapstruct.ap.test.conditional.propertyname.DomainModel;
import org.mapstruct.ap.test.conditional.propertyname.Employee;
import org.mapstruct.ap.test.conditional.propertyname.EmployeeDto;
import org.mapstruct.factory.Mappers;
import java.util.LinkedHashSet;
import java.util.Set;
/**
* @author Filip Hrisafov
* @author Nikola Ivačič
@ -26,11 +31,14 @@ public interface ConditionalMethodInMapperWithAllOptions {
= Mappers.getMapper( ConditionalMethodInMapperWithAllOptions.class );
class PresenceUtils {
Set<String> visited = new LinkedHashSet<>();
Set<String> visitedSourceNames = new LinkedHashSet<>();
Set<String> visitedTargetNames = new LinkedHashSet<>();
Set<String> visitedSources = new LinkedHashSet<>();
Set<String> visitedTargets = new LinkedHashSet<>();
}
@Mapping(target = "country", source = "originCountry")
@Mapping(target = "addresses", source = "originAddresses")
void map(EmployeeDto employeeDto,
@MappingTarget Employee employee,
@Context PresenceUtils utils);
@ -39,12 +47,14 @@ public interface ConditionalMethodInMapperWithAllOptions {
default boolean isNotBlank(String value,
DomainModel source,
@MappingTarget DomainModel target,
@TargetPropertyName String propName,
@SourcePropertyName String sourcePropName,
@TargetPropertyName String targetPropName,
@Context PresenceUtils utils) {
utils.visited.add( propName );
utils.visitedSourceNames.add( sourcePropName );
utils.visitedTargetNames.add( targetPropName );
utils.visitedSources.add( source.getClass().getSimpleName() );
utils.visitedTargets.add( target.getClass().getSimpleName() );
if ( propName.equalsIgnoreCase( "lastName" ) ) {
if ( targetPropName.equalsIgnoreCase( "lastName" ) ) {
return false;
}
return value != null && !value.trim().isEmpty();

View File

@ -3,11 +3,14 @@
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.ap.test.conditional.targetpropertyname;
package org.mapstruct.ap.test.conditional.propertyname.targetpropertyname;
import org.mapstruct.Condition;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.TargetPropertyName;
import org.mapstruct.ap.test.conditional.propertyname.Employee;
import org.mapstruct.ap.test.conditional.propertyname.EmployeeDto;
import org.mapstruct.factory.Mappers;
/**
@ -20,6 +23,8 @@ public interface ConditionalMethodInMapperWithTargetPropertyName {
ConditionalMethodInMapperWithTargetPropertyName INSTANCE
= Mappers.getMapper( ConditionalMethodInMapperWithTargetPropertyName.class );
@Mapping(target = "country", source = "originCountry")
@Mapping(target = "addresses", source = "originAddresses")
Employee map(EmployeeDto employee);
@Condition

View File

@ -3,11 +3,14 @@
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.ap.test.conditional.targetpropertyname;
package org.mapstruct.ap.test.conditional.propertyname.targetpropertyname;
import org.mapstruct.Condition;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.TargetPropertyName;
import org.mapstruct.ap.test.conditional.propertyname.Employee;
import org.mapstruct.ap.test.conditional.propertyname.EmployeeDto;
import org.mapstruct.factory.Mappers;
/**
@ -20,6 +23,8 @@ public interface ConditionalMethodInUsesMapperWithTargetPropertyName {
ConditionalMethodInUsesMapperWithTargetPropertyName INSTANCE
= Mappers.getMapper( ConditionalMethodInUsesMapperWithTargetPropertyName.class );
@Mapping(target = "country", source = "originCountry")
@Mapping(target = "addresses", source = "originAddresses")
Employee map(EmployeeDto employee);
class PresenceUtils {

View File

@ -3,21 +3,29 @@
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.ap.test.conditional.targetpropertyname;
package org.mapstruct.ap.test.conditional.propertyname.targetpropertyname;
import java.util.Deque;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import org.mapstruct.AfterMapping;
import org.mapstruct.BeforeMapping;
import org.mapstruct.Condition;
import org.mapstruct.Context;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.TargetPropertyName;
import org.mapstruct.TargetType;
import org.mapstruct.ap.test.conditional.propertyname.Address;
import org.mapstruct.ap.test.conditional.propertyname.AddressDto;
import org.mapstruct.ap.test.conditional.propertyname.DomainModel;
import org.mapstruct.ap.test.conditional.propertyname.Employee;
import org.mapstruct.ap.test.conditional.propertyname.EmployeeDto;
import org.mapstruct.factory.Mappers;
import java.util.Deque;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.Set;
/**
* @author Nikola Ivačič
*/
@ -27,6 +35,8 @@ public interface ConditionalMethodWithTargetPropertyNameInContextMapper {
ConditionalMethodWithTargetPropertyNameInContextMapper INSTANCE
= Mappers.getMapper( ConditionalMethodWithTargetPropertyNameInContextMapper.class );
@Mapping(target = "country", source = "originCountry")
@Mapping(target = "addresses", source = "originAddresses")
Employee map(EmployeeDto employee, @Context PresenceUtils utils);
Address map(AddressDto addressDto, @Context PresenceUtils utils);
@ -41,6 +51,8 @@ public interface ConditionalMethodWithTargetPropertyNameInContextMapper {
}
}
@Mapping(target = "country", source = "originCountry")
@Mapping(target = "addresses", source = "originAddresses")
Employee map(EmployeeDto employee, @Context PresenceUtilsAllProps utils);
Address map(AddressDto addressDto, @Context PresenceUtilsAllProps utils);
@ -55,6 +67,8 @@ public interface ConditionalMethodWithTargetPropertyNameInContextMapper {
}
}
@Mapping(target = "country", source = "originCountry")
@Mapping(target = "addresses", source = "originAddresses")
Employee map(EmployeeDto employee, @Context PresenceUtilsAllPropsWithSource utils);
Address map(AddressDto addressDto, @Context PresenceUtilsAllPropsWithSource utils);
@ -68,8 +82,11 @@ public interface ConditionalMethodWithTargetPropertyNameInContextMapper {
}
@AfterMapping
default void after(@Context PresenceUtilsAllPropsWithSource utils) {
utils.path.pollLast();
default <T> void after(@TargetType Class<T> targetClass, @Context PresenceUtilsAllPropsWithSource utils) {
// intermediate method for collection mapping must not change the path
if (targetClass != List.class) {
utils.path.pollLast();
}
}
class PresenceUtilsAllPropsWithSource {

View File

@ -3,15 +3,20 @@
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.ap.test.conditional.targetpropertyname;
package org.mapstruct.ap.test.conditional.propertyname.targetpropertyname;
import org.mapstruct.Condition;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.TargetPropertyName;
import org.mapstruct.ap.test.conditional.propertyname.Employee;
import org.mapstruct.ap.test.conditional.propertyname.EmployeeDto;
@Mapper
public interface ErroneousNonStringTargetPropertyNameParameter {
@Mapping(target = "country", source = "originCountry")
@Mapping(target = "addresses", source = "originAddresses")
Employee map(EmployeeDto employee);
@Condition

View File

@ -3,9 +3,16 @@
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.ap.test.conditional.targetpropertyname;
package org.mapstruct.ap.test.conditional.propertyname.targetpropertyname;
import java.util.Collections;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.mapstruct.ap.test.conditional.propertyname.Address;
import org.mapstruct.ap.test.conditional.propertyname.AddressDto;
import org.mapstruct.ap.test.conditional.propertyname.DomainModel;
import org.mapstruct.ap.test.conditional.propertyname.Employee;
import org.mapstruct.ap.test.conditional.propertyname.EmployeeDto;
import org.mapstruct.ap.testutil.IssueKey;
import org.mapstruct.ap.testutil.ProcessorTest;
import org.mapstruct.ap.testutil.WithClasses;
@ -14,8 +21,6 @@ import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic;
import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome;
import org.mapstruct.ap.testutil.runner.GeneratedSource;
import java.util.Collections;
import static org.assertj.core.api.Assertions.assertThat;
/**
@ -46,8 +51,8 @@ public class TargetPropertyNameTest {
EmployeeDto employeeDto = new EmployeeDto();
employeeDto.setFirstName( " " );
employeeDto.setLastName( "Testirovich" );
employeeDto.setCountry( "US" );
employeeDto.setAddresses(
employeeDto.setOriginCountry( "US" );
employeeDto.setOriginAddresses(
Collections.singletonList( new AddressDto( "Testing St. 6" ) )
);
@ -71,8 +76,8 @@ public class TargetPropertyNameTest {
EmployeeDto employeeDto = new EmployeeDto();
employeeDto.setFirstName( " " );
employeeDto.setLastName( "Testirovich" );
employeeDto.setCountry( "US" );
employeeDto.setAddresses(
employeeDto.setOriginCountry( "US" );
employeeDto.setOriginAddresses(
Collections.singletonList( new AddressDto( "Testing St. 6" ) )
);
@ -94,8 +99,8 @@ public class TargetPropertyNameTest {
EmployeeDto employeeDto = new EmployeeDto();
employeeDto.setFirstName( " " );
employeeDto.setLastName( "Testirovich" );
employeeDto.setCountry( "US" );
employeeDto.setAddresses(
employeeDto.setOriginCountry( "US" );
employeeDto.setOriginAddresses(
Collections.singletonList( new AddressDto( "Testing St. 6" ) )
);
@ -122,8 +127,8 @@ public class TargetPropertyNameTest {
EmployeeDto employeeDto = new EmployeeDto();
employeeDto.setFirstName( " " );
employeeDto.setLastName( "Testirovich" );
employeeDto.setCountry( "US" );
employeeDto.setAddresses(
employeeDto.setOriginCountry( "US" );
employeeDto.setOriginAddresses(
Collections.singletonList( new AddressDto( "Testing St. 6" ) )
);
@ -135,7 +140,9 @@ public class TargetPropertyNameTest {
assertThat( employee.getAddresses() )
.extracting( Address::getStreet )
.containsExactly( "Testing St. 6" );
assertThat( utils.visited )
assertThat( utils.visitedSourceNames )
.containsExactlyInAnyOrder( "firstName", "lastName", "title", "originCountry" );
assertThat( utils.visitedTargetNames )
.containsExactlyInAnyOrder( "firstName", "lastName", "title", "country" );
assertThat( utils.visitedSources ).containsExactly( "EmployeeDto" );
assertThat( utils.visitedTargets ).containsExactly( "Employee" );
@ -155,8 +162,8 @@ public class TargetPropertyNameTest {
EmployeeDto employeeDto = new EmployeeDto();
employeeDto.setFirstName( " " );
employeeDto.setLastName( "Testirovich" );
employeeDto.setCountry( "US" );
employeeDto.setAddresses(
employeeDto.setOriginCountry( "US" );
employeeDto.setOriginAddresses(
Collections.singletonList( new AddressDto( "Testing St. 6" ) )
);
@ -185,8 +192,8 @@ public class TargetPropertyNameTest {
EmployeeDto employeeDto = new EmployeeDto();
employeeDto.setLastName( " " );
employeeDto.setCountry( "US" );
employeeDto.setAddresses(
employeeDto.setOriginCountry( "US" );
employeeDto.setOriginAddresses(
Collections.singletonList( new AddressDto( "Testing St. 6" ) )
);
@ -204,8 +211,8 @@ public class TargetPropertyNameTest {
employeeDto = new EmployeeDto();
employeeDto.setLastName( "Tester" );
employeeDto.setCountry( "US" );
employeeDto.setAddresses(
employeeDto.setOriginCountry( "US" );
employeeDto.setOriginAddresses(
Collections.singletonList( new AddressDto( "Testing St. 6" ) )
);
@ -234,15 +241,15 @@ public class TargetPropertyNameTest {
EmployeeDto bossEmployeeDto = new EmployeeDto();
bossEmployeeDto.setLastName( "Boss Tester" );
bossEmployeeDto.setCountry( "US" );
bossEmployeeDto.setAddresses( Collections.singletonList( new AddressDto(
bossEmployeeDto.setOriginCountry( "US" );
bossEmployeeDto.setOriginAddresses( Collections.singletonList( new AddressDto(
"Testing St. 10" ) ) );
employeeDto = new EmployeeDto();
employeeDto.setLastName( "Tester" );
employeeDto.setCountry( "US" );
employeeDto.setOriginCountry( "US" );
employeeDto.setBoss( bossEmployeeDto );
employeeDto.setAddresses(
employeeDto.setOriginAddresses(
Collections.singletonList( new AddressDto( "Testing St. 6" ) )
);
@ -259,26 +266,26 @@ public class TargetPropertyNameTest {
.containsExactly( "Testing St. 10" );
assertThat( allPropsUtilsWithSource.visited )
.containsExactly(
"country",
"addresses",
"addresses.street",
"firstName",
"lastName",
"title",
"country",
"active",
"age",
"boss",
"boss.country",
"boss.addresses",
"boss.addresses.street",
"boss.firstName",
"boss.lastName",
"boss.title",
"boss.country",
"boss.active",
"boss.age",
"boss.boss",
"boss.primaryAddress",
"boss.addresses",
"boss.addresses.street",
"primaryAddress",
"addresses",
"addresses.street"
"primaryAddress"
);
}
@ -293,7 +300,7 @@ public class TargetPropertyNameTest {
@Diagnostic(
kind = javax.tools.Diagnostic.Kind.ERROR,
type = ErroneousNonStringTargetPropertyNameParameter.class,
line = 18,
line = 23,
message = "@TargetPropertyName can only by applied to a String parameter."
)
}