#1571 apply nullvaluecheck strategy on all relevant levels

This commit is contained in:
Sjaak Derksen 2018-09-24 23:35:35 +02:00 committed by GitHub
parent dcddba6853
commit 459f57e805
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 373 additions and 45 deletions

View File

@ -11,6 +11,8 @@ 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 static org.mapstruct.NullValueCheckStrategy.ON_IMPLICIT_CONVERSION;
/** /**
* Configures the mapping between two bean types. * Configures the mapping between two bean types.
* <p> * <p>
@ -61,6 +63,15 @@ public @interface BeanMapping {
*/ */
NullValueMappingStrategy nullValueMappingStrategy() default NullValueMappingStrategy.RETURN_NULL; NullValueMappingStrategy nullValueMappingStrategy() default NullValueMappingStrategy.RETURN_NULL;
/**
* Determines when to include a null check on the source property value of a bean mapping.
*
* Can be overridden by the one on {@link MapperConfig}, {@link Mapper} or {@link Mapping}.
*
* @return strategy how to do null checking
*/
NullValueCheckStrategy nullValueCheckStrategy() default ON_IMPLICIT_CONVERSION;
/** /**
* Default ignore all mappings. All mappings have to be defined manually. No automatic mapping will take place. No * Default ignore all mappings. All mappings have to be defined manually. No automatic mapping will take place. No
* warning will be issued on missing target properties. * warning will be issued on missing target properties.

View File

@ -153,7 +153,7 @@ public @interface Mapper {
/** /**
* Determines when to include a null check on the source property value of a bean mapping. * Determines when to include a null check on the source property value of a bean mapping.
* *
* Can be overridden by the one on {@link MapperConfig} or {@link Mapping}. * Can be overridden by the one on {@link MapperConfig}, {@link BeanMapping} or {@link Mapping}.
* *
* @return strategy how to do null checking * @return strategy how to do null checking
*/ */

View File

@ -138,7 +138,7 @@ public @interface MapperConfig {
/** /**
* Determines when to include a null check on the source property value of a bean mapping. * Determines when to include a null check on the source property value of a bean mapping.
* *
* Can be overridden by the one on {@link Mapper} or {@link Mapping}. * Can be overridden by the one on {@link Mapper}, {@link BeanMapping} or {@link Mapping}.
* *
* @return strategy how to do null checking * @return strategy how to do null checking
*/ */

View File

@ -15,6 +15,8 @@ import java.text.SimpleDateFormat;
import java.text.DecimalFormat; import java.text.DecimalFormat;
import java.util.Date; import java.util.Date;
import static org.mapstruct.NullValueCheckStrategy.ON_IMPLICIT_CONVERSION;
/** /**
* Configures the mapping of one bean attribute or enum constant. * Configures the mapping of one bean attribute or enum constant.
* <p> * <p>
@ -255,4 +257,14 @@ public @interface Mapping {
* @return Default value to set in case the source property is {@code null}. * @return Default value to set in case the source property is {@code null}.
*/ */
String defaultValue() default ""; String defaultValue() default "";
/**
* Determines when to include a null check on the source property value of a bean mapping.
*
* Can be overridden by the one on {@link MapperConfig}, {@link Mapper} or {@link BeanMapping}.
*
* @return strategy how to do null checking
*/
NullValueCheckStrategy nullValueCheckStrategy() default ON_IMPLICIT_CONVERSION;
} }

View File

@ -14,6 +14,8 @@ import java.text.SimpleDateFormat;
import java.text.DecimalFormat; import java.text.DecimalFormat;
import java.util.Date; import java.util.Date;
import static org.mapstruct.NullValueCheckStrategy.ON_IMPLICIT_CONVERSION;
/** /**
* Configures the mapping of one bean attribute or enum constant. * Configures the mapping of one bean attribute or enum constant.
* <p> * <p>
@ -260,4 +262,14 @@ public @interface Mapping {
* @return Default value to set in case the source property is {@code null}. * @return Default value to set in case the source property is {@code null}.
*/ */
String defaultValue() default ""; String defaultValue() default "";
/**
* Determines when to include a null check on the source property value of a bean mapping.
*
* Can be overridden by the one on {@link MapperConfig}, {@link Mapper} or {@link BeanMapping}.
*
* @return strategy how to do null checking
*/
NullValueCheckStrategy nullValueCheckStrategy() default ON_IMPLICIT_CONVERSION;
} }

View File

@ -75,8 +75,6 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
private Set<String> targetProperties; private Set<String> targetProperties;
private final List<PropertyMapping> propertyMappings = new ArrayList<PropertyMapping>(); private final List<PropertyMapping> propertyMappings = new ArrayList<PropertyMapping>();
private final Set<Parameter> unprocessedSourceParameters = new HashSet<Parameter>(); private final Set<Parameter> unprocessedSourceParameters = new HashSet<Parameter>();
private NullValueMappingStrategyPrism nullValueMappingStrategy;
private SelectionParameters selectionParameters;
private final Set<String> existingVariableNames = new HashSet<String>(); private final Set<String> existingVariableNames = new HashSet<String>();
private Map<String, List<Mapping>> methodMappings; private Map<String, List<Mapping>> methodMappings;
private SingleMappingByTargetPropertyNameFunction singleMapping; private SingleMappingByTargetPropertyNameFunction singleMapping;
@ -135,16 +133,6 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
return this; return this;
} }
public Builder selectionParameters(SelectionParameters selectionParameters) {
this.selectionParameters = selectionParameters;
return this;
}
public Builder nullValueMappingStrategy(NullValueMappingStrategyPrism nullValueMappingStrategy) {
this.nullValueMappingStrategy = nullValueMappingStrategy;
return this;
}
public BeanMappingMethod build() { public BeanMappingMethod build() {
// map properties with mapping // map properties with mapping
boolean mappingErrorOccured = handleDefinedMappings(); boolean mappingErrorOccured = handleDefinedMappings();
@ -168,12 +156,20 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
reportErrorForUnmappedTargetPropertiesIfRequired(); reportErrorForUnmappedTargetPropertiesIfRequired();
reportErrorForUnmappedSourcePropertiesIfRequired(); reportErrorForUnmappedSourcePropertiesIfRequired();
// get bean mapping (when specified as annotation )
BeanMapping beanMapping = method.getMappingOptions().getBeanMapping();
BeanMappingPrism beanMappingPrism = BeanMappingPrism.getInstanceOn( method.getExecutable() );
// mapNullToDefault // mapNullToDefault
NullValueMappingStrategyPrism nullValueMappingStrategy =
beanMapping != null ? beanMapping.getNullValueMappingStrategy() : null;
boolean mapNullToDefault = method.getMapperConfiguration().isMapToDefault( nullValueMappingStrategy ); boolean mapNullToDefault = method.getMapperConfiguration().isMapToDefault( nullValueMappingStrategy );
BeanMappingPrism beanMappingPrism = BeanMappingPrism.getInstanceOn( method.getExecutable() ); // selectionParameters
SelectionParameters selectionParameters = beanMapping != null ? beanMapping.getSelectionParameters() : null;
// check if there's a factory method for the result type
MethodReference factoryMethod = null; MethodReference factoryMethod = null;
if ( !method.isUpdateMethod() ) { if ( !method.isUpdateMethod() ) {
factoryMethod = ObjectFactoryMethodResolver.getFactoryMethod( factoryMethod = ObjectFactoryMethodResolver.getFactoryMethod(
@ -481,6 +477,7 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
.dependsOn( mapping.getDependsOn() ) .dependsOn( mapping.getDependsOn() )
.defaultValue( mapping.getDefaultValue() ) .defaultValue( mapping.getDefaultValue() )
.defaultJavaExpression( mapping.getDefaultJavaExpression() ) .defaultJavaExpression( mapping.getDefaultJavaExpression() )
.nullValueCheckStrategyPrism( mapping.getNullValueCheckStrategy() )
.build(); .build();
handledTargets.add( propertyName ); handledTargets.add( propertyName );
unprocessedSourceParameters.remove( sourceRef.getParameter() ); unprocessedSourceParameters.remove( sourceRef.getParameter() );
@ -597,6 +594,8 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
.existingVariableNames( existingVariableNames ) .existingVariableNames( existingVariableNames )
.dependsOn( mapping != null ? mapping.getDependsOn() : Collections.<String>emptyList() ) .dependsOn( mapping != null ? mapping.getDependsOn() : Collections.<String>emptyList() )
.forgeMethodWithMappingOptions( extractAdditionalOptions( targetPropertyName, false ) ) .forgeMethodWithMappingOptions( extractAdditionalOptions( targetPropertyName, false ) )
.nullValueCheckStrategyPrism( mapping != null ? mapping.getNullValueCheckStrategy()
: null )
.build(); .build();
unprocessedSourceParameters.remove( sourceParameter ); unprocessedSourceParameters.remove( sourceParameter );
@ -660,6 +659,7 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
.existingVariableNames( existingVariableNames ) .existingVariableNames( existingVariableNames )
.dependsOn( mapping != null ? mapping.getDependsOn() : Collections.<String>emptyList() ) .dependsOn( mapping != null ? mapping.getDependsOn() : Collections.<String>emptyList() )
.forgeMethodWithMappingOptions( extractAdditionalOptions( targetProperty.getKey(), false ) ) .forgeMethodWithMappingOptions( extractAdditionalOptions( targetProperty.getKey(), false ) )
.nullValueCheckStrategyPrism( mapping != null ? mapping.getNullValueCheckStrategy() : null )
.build(); .build();
propertyMappings.add( propertyMapping ); propertyMappings.add( propertyMapping );

View File

@ -57,6 +57,7 @@ public class CollectionAssignmentBuilder {
private PropertyMapping.TargetWriteAccessorType targetAccessorType; private PropertyMapping.TargetWriteAccessorType targetAccessorType;
private Assignment assignment; private Assignment assignment;
private SourceRHS sourceRHS; private SourceRHS sourceRHS;
private NullValueCheckStrategyPrism nullValueCheckStrategy;
public CollectionAssignmentBuilder mappingBuilderContext(MappingBuilderContext ctx) { public CollectionAssignmentBuilder mappingBuilderContext(MappingBuilderContext ctx) {
this.ctx = ctx; this.ctx = ctx;
@ -108,6 +109,11 @@ public class CollectionAssignmentBuilder {
return this; return this;
} }
public CollectionAssignmentBuilder nullValueCheckStrategy( NullValueCheckStrategyPrism nullValueCheckStrategy ) {
this.nullValueCheckStrategy = nullValueCheckStrategy;
return this;
}
public Assignment build() { public Assignment build() {
Assignment result = assignment; Assignment result = assignment;
@ -146,14 +152,14 @@ public class CollectionAssignmentBuilder {
result, result,
method.getThrownTypes(), method.getThrownTypes(),
targetType, targetType,
method.getMapperConfiguration().getNullValueCheckStrategy(), nullValueCheckStrategy,
ctx.getTypeFactory(), ctx.getTypeFactory(),
PropertyMapping.TargetWriteAccessorType.isFieldAssignment( targetAccessorType ), PropertyMapping.TargetWriteAccessorType.isFieldAssignment( targetAccessorType ),
mapNullToDefault() mapNullToDefault()
); );
} }
else if ( result.getType() == Assignment.AssignmentType.DIRECT || else if ( result.getType() == Assignment.AssignmentType.DIRECT ||
method.getMapperConfiguration().getNullValueCheckStrategy() == NullValueCheckStrategyPrism.ALWAYS ) { nullValueCheckStrategy == NullValueCheckStrategyPrism.ALWAYS ) {
result = new SetterWrapperForCollectionsAndMapsWithNullCheck( result = new SetterWrapperForCollectionsAndMapsWithNullCheck(
result, result,
@ -199,4 +205,5 @@ public class CollectionAssignmentBuilder {
return method.getMapperConfiguration().getNullValueMappingStrategy() return method.getMapperConfiguration().getNullValueMappingStrategy()
== NullValueMappingStrategyPrism.RETURN_DEFAULT; == NullValueMappingStrategyPrism.RETURN_DEFAULT;
} }
} }

View File

@ -5,17 +5,11 @@
*/ */
package org.mapstruct.ap.internal.model; package org.mapstruct.ap.internal.model;
import static org.mapstruct.ap.internal.model.common.Assignment.AssignmentType.DIRECT;
import static org.mapstruct.ap.internal.prism.NullValueCheckStrategyPrism.ALWAYS;
import static org.mapstruct.ap.internal.util.Collections.first;
import static org.mapstruct.ap.internal.util.Collections.last;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.ExecutableElement;
import javax.lang.model.type.DeclaredType; import javax.lang.model.type.DeclaredType;
@ -33,6 +27,7 @@ import org.mapstruct.ap.internal.model.common.ModelElement;
import org.mapstruct.ap.internal.model.common.Parameter; import org.mapstruct.ap.internal.model.common.Parameter;
import org.mapstruct.ap.internal.model.common.SourceRHS; 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.source.BeanMapping;
import org.mapstruct.ap.internal.model.source.ForgedMethod; import org.mapstruct.ap.internal.model.source.ForgedMethod;
import org.mapstruct.ap.internal.model.source.ForgedMethodHistory; import org.mapstruct.ap.internal.model.source.ForgedMethodHistory;
import org.mapstruct.ap.internal.model.source.MappingOptions; import org.mapstruct.ap.internal.model.source.MappingOptions;
@ -52,6 +47,11 @@ import org.mapstruct.ap.internal.util.Strings;
import org.mapstruct.ap.internal.util.ValueProvider; import org.mapstruct.ap.internal.util.ValueProvider;
import org.mapstruct.ap.internal.util.accessor.Accessor; import org.mapstruct.ap.internal.util.accessor.Accessor;
import static org.mapstruct.ap.internal.model.common.Assignment.AssignmentType.DIRECT;
import static org.mapstruct.ap.internal.prism.NullValueCheckStrategyPrism.ALWAYS;
import static org.mapstruct.ap.internal.util.Collections.first;
import static org.mapstruct.ap.internal.util.Collections.last;
/** /**
* Represents the mapping between a source and target property, e.g. from {@code String Source#foo} to * Represents the mapping between a source and target property, e.g. from {@code String Source#foo} to
* {@code int Target#bar}. Name and type of source and target property can differ. If they have different types, the * {@code int Target#bar}. Name and type of source and target property can differ. If they have different types, the
@ -197,6 +197,7 @@ public class PropertyMapping extends ModelElement {
private MappingOptions forgeMethodWithMappingOptions; private MappingOptions forgeMethodWithMappingOptions;
private boolean forceUpdateMethod; private boolean forceUpdateMethod;
private boolean forgedNamedBased = true; private boolean forgedNamedBased = true;
private NullValueCheckStrategyPrism nullValueCheckStrategyPrism;
PropertyMappingBuilder() { PropertyMappingBuilder() {
super( PropertyMappingBuilder.class ); super( PropertyMappingBuilder.class );
@ -253,6 +254,12 @@ public class PropertyMapping extends ModelElement {
return this; return this;
} }
public PropertyMappingBuilder nullValueCheckStrategyPrism(
NullValueCheckStrategyPrism nullValueCheckStrategyPrism) {
this.nullValueCheckStrategyPrism = nullValueCheckStrategyPrism;
return this;
}
public PropertyMapping build() { public PropertyMapping build() {
// handle source // handle source
this.rightHandSide = getSourceRHS( sourceReference ); this.rightHandSide = getSourceRHS( sourceReference );
@ -427,11 +434,16 @@ public class PropertyMapping extends ModelElement {
!rhs.isSourceReferenceParameter(), mapNullToDefault ); !rhs.isSourceReferenceParameter(), mapNullToDefault );
} }
else { else {
NullValueCheckStrategyPrism nvcs = method.getMapperConfiguration().getNullValueCheckStrategy(); return new SetterWrapper( rhs, method.getThrownTypes(), getNvcs(), isFieldAssignment(), targetType );
return new SetterWrapper( rhs, method.getThrownTypes(), nvcs, isFieldAssignment(), targetType );
} }
} }
private NullValueCheckStrategyPrism getNvcs() {
BeanMapping beanMapping = method.getMappingOptions().getBeanMapping();
NullValueCheckStrategyPrism nvcsBean = beanMapping != null ? beanMapping.getNullValueCheckStrategy() : null;
return method.getMapperConfiguration().getNullValueCheckStrategy( nvcsBean, nullValueCheckStrategyPrism );
}
private Assignment assignToPlainViaAdder( Assignment rightHandSide) { private Assignment assignToPlainViaAdder( Assignment rightHandSide) {
Assignment result = rightHandSide; Assignment result = rightHandSide;
@ -461,6 +473,7 @@ public class PropertyMapping extends ModelElement {
.targetAccessorType( targetAccessorType ) .targetAccessorType( targetAccessorType )
.rightHandSide( rightHandSide ) .rightHandSide( rightHandSide )
.assignment( rhs ) .assignment( rhs )
.nullValueCheckStrategy( getNvcs() )
.build(); .build();
} }

View File

@ -13,6 +13,7 @@ import javax.lang.model.util.Types;
import org.mapstruct.ap.internal.prism.BeanMappingPrism; import org.mapstruct.ap.internal.prism.BeanMappingPrism;
import org.mapstruct.ap.internal.prism.BuilderPrism; import org.mapstruct.ap.internal.prism.BuilderPrism;
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.ReportingPolicyPrism; import org.mapstruct.ap.internal.prism.ReportingPolicyPrism;
import org.mapstruct.ap.internal.util.FormattingMessager; import org.mapstruct.ap.internal.util.FormattingMessager;
@ -27,6 +28,7 @@ public class BeanMapping {
private final SelectionParameters selectionParameters; private final SelectionParameters selectionParameters;
private final NullValueMappingStrategyPrism nullValueMappingStrategy; private final NullValueMappingStrategyPrism nullValueMappingStrategy;
private final NullValueCheckStrategyPrism nullValueCheckStrategy;
private final ReportingPolicyPrism reportingPolicy; private final ReportingPolicyPrism reportingPolicy;
private final boolean ignoreByDefault; private final boolean ignoreByDefault;
private final List<String> ignoreUnmappedSourceProperties; private final List<String> ignoreUnmappedSourceProperties;
@ -42,6 +44,7 @@ public class BeanMapping {
return new BeanMapping( return new BeanMapping(
map.selectionParameters, map.selectionParameters,
map.nullValueMappingStrategy, map.nullValueMappingStrategy,
map.nullValueCheckStrategy,
map.reportingPolicy, map.reportingPolicy,
false, false,
map.ignoreUnmappedSourceProperties, map.ignoreUnmappedSourceProperties,
@ -63,6 +66,11 @@ public class BeanMapping {
? null ? null
: NullValueMappingStrategyPrism.valueOf( beanMapping.nullValueMappingStrategy() ); : NullValueMappingStrategyPrism.valueOf( beanMapping.nullValueMappingStrategy() );
NullValueCheckStrategyPrism nullValueCheckStrategy =
null == beanMapping.values.nullValueCheckStrategy()
? null
: NullValueCheckStrategyPrism.valueOf( beanMapping.nullValueCheckStrategy() );
boolean ignoreByDefault = beanMapping.ignoreByDefault(); boolean ignoreByDefault = beanMapping.ignoreByDefault();
BuilderPrism builderMapping = null; BuilderPrism builderMapping = null;
if ( beanMapping.values.builder() != null ) { if ( beanMapping.values.builder() != null ) {
@ -71,7 +79,7 @@ public class BeanMapping {
if ( !resultTypeIsDefined && beanMapping.qualifiedBy().isEmpty() && beanMapping.qualifiedByName().isEmpty() if ( !resultTypeIsDefined && beanMapping.qualifiedBy().isEmpty() && beanMapping.qualifiedByName().isEmpty()
&& beanMapping.ignoreUnmappedSourceProperties().isEmpty() && beanMapping.ignoreUnmappedSourceProperties().isEmpty()
&& ( nullValueMappingStrategy == null ) && !ignoreByDefault && ( nullValueMappingStrategy == null ) && ( nullValueCheckStrategy == null ) && !ignoreByDefault
&& builderMapping == null ) { && builderMapping == null ) {
messager.printMessage( method, Message.BEANMAPPING_NO_ELEMENTS ); messager.printMessage( method, Message.BEANMAPPING_NO_ELEMENTS );
@ -88,6 +96,7 @@ public class BeanMapping {
return new BeanMapping( return new BeanMapping(
cmp, cmp,
nullValueMappingStrategy, nullValueMappingStrategy,
nullValueCheckStrategy,
null, null,
ignoreByDefault, ignoreByDefault,
beanMapping.ignoreUnmappedSourceProperties(), beanMapping.ignoreUnmappedSourceProperties(),
@ -102,14 +111,24 @@ public class BeanMapping {
* @return bean mapping that needs to be used for Mappings * @return bean mapping that needs to be used for Mappings
*/ */
public static BeanMapping forForgedMethods() { public static BeanMapping forForgedMethods() {
return new BeanMapping( null, null, ReportingPolicyPrism.IGNORE, false, Collections.<String>emptyList(), null ); return new BeanMapping(
null,
null,
null,
ReportingPolicyPrism.IGNORE,
false,
Collections.<String>emptyList(),
null
);
} }
private BeanMapping(SelectionParameters selectionParameters, NullValueMappingStrategyPrism nvms, private BeanMapping(SelectionParameters selectionParameters, NullValueMappingStrategyPrism nvms,
NullValueCheckStrategyPrism nvcs,
ReportingPolicyPrism reportingPolicy, boolean ignoreByDefault, ReportingPolicyPrism reportingPolicy, boolean ignoreByDefault,
List<String> ignoreUnmappedSourceProperties, BuilderPrism builder) { List<String> ignoreUnmappedSourceProperties, BuilderPrism builder) {
this.selectionParameters = selectionParameters; this.selectionParameters = selectionParameters;
this.nullValueMappingStrategy = nvms; this.nullValueMappingStrategy = nvms;
this.nullValueCheckStrategy = nvcs;
this.reportingPolicy = reportingPolicy; this.reportingPolicy = reportingPolicy;
this.ignoreByDefault = ignoreByDefault; this.ignoreByDefault = ignoreByDefault;
this.ignoreUnmappedSourceProperties = ignoreUnmappedSourceProperties; this.ignoreUnmappedSourceProperties = ignoreUnmappedSourceProperties;
@ -124,6 +143,10 @@ public class BeanMapping {
return nullValueMappingStrategy; return nullValueMappingStrategy;
} }
public NullValueCheckStrategyPrism getNullValueCheckStrategy() {
return nullValueCheckStrategy;
}
public ReportingPolicyPrism getReportingPolicy() { public ReportingPolicyPrism getReportingPolicy() {
return reportingPolicy; return reportingPolicy;
} }

View File

@ -26,6 +26,7 @@ import org.mapstruct.ap.internal.model.common.Parameter;
import org.mapstruct.ap.internal.model.common.TypeFactory; import org.mapstruct.ap.internal.model.common.TypeFactory;
import org.mapstruct.ap.internal.prism.MappingPrism; import org.mapstruct.ap.internal.prism.MappingPrism;
import org.mapstruct.ap.internal.prism.MappingsPrism; import org.mapstruct.ap.internal.prism.MappingsPrism;
import org.mapstruct.ap.internal.prism.NullValueCheckStrategyPrism;
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;
import org.mapstruct.ap.internal.util.Message; import org.mapstruct.ap.internal.util.Message;
@ -56,6 +57,7 @@ public class Mapping {
private final AnnotationValue sourceAnnotationValue; private final AnnotationValue sourceAnnotationValue;
private final AnnotationValue targetAnnotationValue; private final AnnotationValue targetAnnotationValue;
private final AnnotationValue dependsOnAnnotationValue; private final AnnotationValue dependsOnAnnotationValue;
private final NullValueCheckStrategyPrism nullValueCheckStrategy;
private SourceReference sourceReference; private SourceReference sourceReference;
private TargetReference targetReference; private TargetReference targetReference;
@ -181,6 +183,11 @@ public class Mapping {
typeUtils typeUtils
); );
NullValueCheckStrategyPrism nullValueCheckStrategy =
null == mappingPrism.values.nullValueCheckStrategy()
? null
: NullValueCheckStrategyPrism.valueOf( mappingPrism.nullValueCheckStrategy() );
return new Mapping( return new Mapping(
source, source,
constant, constant,
@ -195,7 +202,8 @@ public class Mapping {
formattingParam, formattingParam,
selectionParams, selectionParams,
mappingPrism.values.dependsOn(), mappingPrism.values.dependsOn(),
dependsOn dependsOn,
nullValueCheckStrategy
); );
} }
@ -214,7 +222,8 @@ public class Mapping {
null, null,
null, null,
null, null,
new ArrayList() new ArrayList(),
null
); );
} }
@ -223,7 +232,8 @@ public class Mapping {
String targetName, String defaultValue, boolean isIgnored, AnnotationMirror mirror, String targetName, String defaultValue, boolean isIgnored, AnnotationMirror mirror,
AnnotationValue sourceAnnotationValue, AnnotationValue targetAnnotationValue, AnnotationValue sourceAnnotationValue, AnnotationValue targetAnnotationValue,
FormattingParameters formattingParameters, SelectionParameters selectionParameters, FormattingParameters formattingParameters, SelectionParameters selectionParameters,
AnnotationValue dependsOnAnnotationValue, List<String> dependsOn ) { AnnotationValue dependsOnAnnotationValue, List<String> dependsOn,
NullValueCheckStrategyPrism nullValueCheckStrategy ) {
this.sourceName = sourceName; this.sourceName = sourceName;
this.constant = constant; this.constant = constant;
this.javaExpression = javaExpression; this.javaExpression = javaExpression;
@ -238,6 +248,7 @@ public class Mapping {
this.selectionParameters = selectionParameters; this.selectionParameters = selectionParameters;
this.dependsOnAnnotationValue = dependsOnAnnotationValue; this.dependsOnAnnotationValue = dependsOnAnnotationValue;
this.dependsOn = dependsOn; this.dependsOn = dependsOn;
this.nullValueCheckStrategy = nullValueCheckStrategy;
} }
private Mapping( Mapping mapping, TargetReference targetReference ) { private Mapping( Mapping mapping, TargetReference targetReference ) {
@ -257,6 +268,7 @@ public class Mapping {
this.dependsOn = mapping.dependsOn; this.dependsOn = mapping.dependsOn;
this.sourceReference = mapping.sourceReference; this.sourceReference = mapping.sourceReference;
this.targetReference = targetReference; this.targetReference = targetReference;
this.nullValueCheckStrategy = mapping.nullValueCheckStrategy;
} }
private Mapping( Mapping mapping, SourceReference sourceReference ) { private Mapping( Mapping mapping, SourceReference sourceReference ) {
@ -276,6 +288,7 @@ public class Mapping {
this.dependsOn = mapping.dependsOn; this.dependsOn = mapping.dependsOn;
this.sourceReference = sourceReference; this.sourceReference = sourceReference;
this.targetReference = mapping.targetReference; this.targetReference = mapping.targetReference;
this.nullValueCheckStrategy = mapping.nullValueCheckStrategy;
} }
private static String getExpression(MappingPrism mappingPrism, ExecutableElement element, private static String getExpression(MappingPrism mappingPrism, ExecutableElement element,
@ -441,6 +454,10 @@ public class Mapping {
return targetReference; return targetReference;
} }
public NullValueCheckStrategyPrism getNullValueCheckStrategy() {
return nullValueCheckStrategy;
}
public Mapping popTargetReference() { public Mapping popTargetReference() {
if ( targetReference != null ) { if ( targetReference != null ) {
TargetReference newTargetReference = targetReference.pop(); TargetReference newTargetReference = targetReference.pop();
@ -488,7 +505,8 @@ public class Mapping {
formattingParameters, formattingParameters,
selectionParameters, selectionParameters,
dependsOnAnnotationValue, dependsOnAnnotationValue,
Collections.<String>emptyList() Collections.<String>emptyList(),
nullValueCheckStrategy
); );
reverse.init( reverse.init(
@ -529,7 +547,8 @@ public class Mapping {
formattingParameters, formattingParameters,
selectionParameters, selectionParameters,
dependsOnAnnotationValue, dependsOnAnnotationValue,
dependsOn dependsOn,
nullValueCheckStrategy
); );
if ( sourceReference != null ) { if ( sourceReference != null ) {

View File

@ -7,7 +7,7 @@ package org.mapstruct.ap.internal.prism;
/** /**
* Prism for the enum {@link org.mapstruct.SourceValuePresenceCheckStrategy} * Prism for the enum {@link org.mapstruct.NullValueCheckStrategy}
* *
* @author Sean Huang * @author Sean Huang
*/ */

View File

@ -365,19 +365,11 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Sourc
} }
else { else {
NullValueMappingStrategyPrism nullValueMappingStrategy = null;
SelectionParameters selectionParameters = null;
if ( mappingOptions.getBeanMapping() != null ) {
nullValueMappingStrategy = mappingOptions.getBeanMapping().getNullValueMappingStrategy();
selectionParameters = mappingOptions.getBeanMapping().getSelectionParameters();
}
BeanMappingMethod.Builder builder = new BeanMappingMethod.Builder(); BeanMappingMethod.Builder builder = new BeanMappingMethod.Builder();
BeanMappingMethod beanMappingMethod = builder BeanMappingMethod beanMappingMethod = builder
.mappingContext( mappingContext ) .mappingContext( mappingContext )
.sourceMethod( method ) .sourceMethod( method )
.nullValueMappingStrategy( nullValueMappingStrategy )
.selectionParameters( selectionParameters )
.build(); .build();
if ( beanMappingMethod != null ) { if ( beanMappingMethod != null ) {

View File

@ -8,7 +8,6 @@ package org.mapstruct.ap.internal.util;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element; import javax.lang.model.element.Element;
import javax.lang.model.type.DeclaredType; import javax.lang.model.type.DeclaredType;
@ -148,8 +147,15 @@ public class MapperConfiguration {
} }
} }
public NullValueCheckStrategyPrism getNullValueCheckStrategy() { public NullValueCheckStrategyPrism getNullValueCheckStrategy(NullValueCheckStrategyPrism beanPrism,
if ( mapperConfigPrism != null && mapperPrism.values.nullValueCheckStrategy() == null ) { NullValueCheckStrategyPrism mappingPrism) {
if ( mappingPrism != null ) {
return mappingPrism;
}
else if ( beanPrism != null ) {
return beanPrism;
}
else if ( mapperConfigPrism != null && mapperPrism.values.nullValueCheckStrategy() == null ) {
return NullValueCheckStrategyPrism.valueOf( mapperConfigPrism.nullValueCheckStrategy() ); return NullValueCheckStrategyPrism.valueOf( mapperConfigPrism.nullValueCheckStrategy() );
} }
else { else {

View File

@ -0,0 +1,28 @@
/*
* 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.nullcheck.strategy;
public class HouseDto {
private String owner;
private Integer number;
public String getOwner() {
return owner;
}
public void setOwner(String owner) {
this.owner = owner;
}
public Integer getNumber() {
return number;
}
public void setNumber(Integer number) {
this.number = number;
}
}

View File

@ -0,0 +1,41 @@
/*
* 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.nullcheck.strategy;
public class HouseEntity {
private String owner;
private boolean ownerSet = false;
private Integer number;
private boolean numberSet = false;
public String getOwner() {
return owner;
}
public void setOwner(String owner) {
ownerSet = true;
this.owner = owner;
}
public boolean ownerSet() {
return ownerSet;
}
public Integer getNumber() {
return number;
}
public void setNumber(Integer number) {
numberSet = true;
this.number = number;
}
public boolean numberSet() {
return numberSet;
}
}

View File

@ -0,0 +1,28 @@
/*
* 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.nullcheck.strategy;
import org.mapstruct.BeanMapping;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.NullValueCheckStrategy;
import org.mapstruct.factory.Mappers;
@Mapper(nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS)
public interface HouseMapper {
HouseMapper INSTANCE = Mappers.getMapper( HouseMapper.class );
HouseEntity mapWithNvcsOnMapper(HouseDto in);
@BeanMapping(nullValueCheckStrategy = NullValueCheckStrategy.ON_IMPLICIT_CONVERSION)
HouseEntity mapWithNvcsOnBean(HouseDto in);
@BeanMapping(nullValueCheckStrategy = NullValueCheckStrategy.ON_IMPLICIT_CONVERSION)
@Mapping(target = "number", nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS)
HouseEntity mapWithNvcsOnMapping(HouseDto in);
}

View File

@ -0,0 +1,15 @@
/*
* 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.nullcheck.strategy;
import org.mapstruct.MapperConfig;
import org.mapstruct.NullValueCheckStrategy;
@MapperConfig(
nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS
)
public interface HouseMapperConfig {
}

View File

@ -0,0 +1,28 @@
/*
* 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.nullcheck.strategy;
import org.mapstruct.BeanMapping;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.NullValueCheckStrategy;
import org.mapstruct.factory.Mappers;
@Mapper(config = HouseMapperConfig.class)
public interface HouseMapperWithConfig {
HouseMapperWithConfig INSTANCE = Mappers.getMapper( HouseMapperWithConfig.class );
HouseEntity mapWithNvcsOnMapper(HouseDto in);
@BeanMapping(nullValueCheckStrategy = NullValueCheckStrategy.ON_IMPLICIT_CONVERSION)
HouseEntity mapWithNvcsOnBean(HouseDto in);
@BeanMapping(nullValueCheckStrategy = NullValueCheckStrategy.ON_IMPLICIT_CONVERSION)
@Mapping(target = "number", nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS)
HouseEntity mapWithNvcsOnMapping(HouseDto in);
}

View File

@ -0,0 +1,93 @@
/*
* 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.nullcheck.strategy;
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;
@IssueKey("1571")
@WithClasses({
HouseDto.class,
HouseEntity.class,
HouseMapper.class,
HouseMapperConfig.class,
HouseMapperWithConfig.class
})
@RunWith(AnnotationProcessorTestRunner.class)
public class NullValueCheckTest {
@Test
public void testDefinedOnMapper() {
HouseEntity entity = HouseMapper.INSTANCE.mapWithNvcsOnMapper( new HouseDto() );
assertThat( entity ).isNotNull();
assertThat( entity.ownerSet() ).isFalse();
assertThat( entity.numberSet() ).isFalse();
}
@Test
public void testDefinedOnBean() {
HouseEntity entity = HouseMapper.INSTANCE.mapWithNvcsOnBean( new HouseDto() );
assertThat( entity ).isNotNull();
assertThat( entity.ownerSet() ).isTrue();
assertThat( entity.numberSet() ).isTrue();
}
@Test
public void testDefinedOnMapping() {
HouseEntity entity = HouseMapper.INSTANCE.mapWithNvcsOnMapping( new HouseDto() );
assertThat( entity ).isNotNull();
assertThat( entity.ownerSet() ).isTrue();
assertThat( entity.numberSet() ).isFalse();
}
@Test
public void testDefinedOnConfig() {
HouseEntity entity = HouseMapperWithConfig.INSTANCE.mapWithNvcsOnMapper( new HouseDto() );
assertThat( entity ).isNotNull();
assertThat( entity.ownerSet() ).isFalse();
assertThat( entity.numberSet() ).isFalse();
}
@Test
public void testDefinedOnConfigAndBean() {
HouseEntity entity = HouseMapperWithConfig.INSTANCE.mapWithNvcsOnBean( new HouseDto() );
assertThat( entity ).isNotNull();
assertThat( entity.ownerSet() ).isTrue();
assertThat( entity.numberSet() ).isTrue();
}
@Test
public void testDefinedOnConfigAndMapping() {
HouseEntity entity = HouseMapperWithConfig.INSTANCE.mapWithNvcsOnMapping( new HouseDto() );
assertThat( entity ).isNotNull();
assertThat( entity.ownerSet() ).isTrue();
assertThat( entity.numberSet() ).isFalse();
}
}