diff --git a/core/src/main/java/org/mapstruct/BeanMapping.java b/core/src/main/java/org/mapstruct/BeanMapping.java index 2492477a2..b510b442d 100644 --- a/core/src/main/java/org/mapstruct/BeanMapping.java +++ b/core/src/main/java/org/mapstruct/BeanMapping.java @@ -54,7 +54,7 @@ public @interface BeanMapping { String[] qualifiedByName() default { }; /** - * The strategy to be applied when {@code null} is passed as source value to this bean mapping. If no + * The strategy to be applied when {@code null} is passed as source bean argument value to this bean mapping. If no * strategy is configured, the strategy given via {@link MapperConfig#nullValueMappingStrategy()} or * {@link Mapper#nullValueMappingStrategy()} will be applied, using {@link NullValueMappingStrategy#RETURN_NULL} * by default. @@ -63,6 +63,18 @@ public @interface BeanMapping { */ NullValueMappingStrategy nullValueMappingStrategy() default NullValueMappingStrategy.RETURN_NULL; + /** + * The strategy to be applied when a source bean property is {@code null} or not present. If no strategy is + * configured, the strategy given via {@link MapperConfig#nullValuePropertyMappingStrategy()} or + * {@link Mapper#nullValuePropertyMappingStrategy()} will be applied, + * {@link NullValuePropertyMappingStrategy#SET_TO_NULL} will be used by default. + * + * @return The strategy to be applied when {@code null} is passed as source property value or the source property + * is not present. + */ + NullValuePropertyMappingStrategy nullValuePropertyMappingStrategy() + default NullValuePropertyMappingStrategy.SET_TO_NULL; + /** * Determines when to include a null check on the source property value of a bean mapping. * diff --git a/core/src/main/java/org/mapstruct/Mapper.java b/core/src/main/java/org/mapstruct/Mapper.java index c86485e94..7c3f8f9e5 100644 --- a/core/src/main/java/org/mapstruct/Mapper.java +++ b/core/src/main/java/org/mapstruct/Mapper.java @@ -129,14 +129,25 @@ public @interface Mapper { CollectionMappingStrategy collectionMappingStrategy() default CollectionMappingStrategy.ACCESSOR_ONLY; /** - * The strategy to be applied when {@code null} is passed as source value to the methods of this mapper. If no - * strategy is configured, the strategy given via {@link MapperConfig#nullValueMappingStrategy()} will be applied, - * using {@link NullValueMappingStrategy#RETURN_NULL} by default. + * The strategy to be applied when {@code null} is passed as source argument value to the methods of this mapper. + * If no strategy is configured, the strategy given via {@link MapperConfig#nullValueMappingStrategy()} will be + * applied, using {@link NullValueMappingStrategy#RETURN_NULL} by default. * * @return The strategy to be applied when {@code null} is passed as source value to the methods of this mapper. */ NullValueMappingStrategy nullValueMappingStrategy() default NullValueMappingStrategy.RETURN_NULL; + /** + * The strategy to be applied when a source bean property is {@code null} or not present. If no strategy is + * configured, the strategy given via {@link MapperConfig#nullValuePropertyMappingStrategy()} will be applied, + * {@link NullValuePropertyMappingStrategy#SET_TO_NULL} will be used by default. + * + * @return The strategy to be applied when {@code null} is passed as source property value or the source property + * is not present. + */ + NullValuePropertyMappingStrategy nullValuePropertyMappingStrategy() default + NullValuePropertyMappingStrategy.SET_TO_NULL; + /** * The strategy to use for applying method-level configuration annotations of prototype methods in the interface * specified with {@link #config()}. Annotations that can be inherited are for example {@link Mapping}, diff --git a/core/src/main/java/org/mapstruct/MapperConfig.java b/core/src/main/java/org/mapstruct/MapperConfig.java index cbcd15115..85991f527 100644 --- a/core/src/main/java/org/mapstruct/MapperConfig.java +++ b/core/src/main/java/org/mapstruct/MapperConfig.java @@ -115,13 +115,23 @@ public @interface MapperConfig { CollectionMappingStrategy collectionMappingStrategy() default CollectionMappingStrategy.ACCESSOR_ONLY; /** - * The strategy to be applied when {@code null} is passed as source value to mapping methods. If no strategy is - * configured, {@link NullValueMappingStrategy#RETURN_NULL} will be used by default. + * The strategy to be applied when {@code null} is passed as source argument value to mapping methods. If no + * strategy is configured, {@link NullValueMappingStrategy#RETURN_NULL} will be used by default. * * @return The strategy to be applied when {@code null} is passed as source value to mapping methods. */ NullValueMappingStrategy nullValueMappingStrategy() default NullValueMappingStrategy.RETURN_NULL; + /** + * The strategy to be applied when a source bean property is {@code null} or not present. If no strategy is + * configured, {@link NullValuePropertyMappingStrategy#SET_TO_NULL} will be used by default. + * + * @return The strategy to be applied when {@code null} is passed as source property value or the source property + * is not present. + */ + NullValuePropertyMappingStrategy nullValuePropertyMappingStrategy() default + NullValuePropertyMappingStrategy.SET_TO_NULL; + /** * The strategy to use for applying method-level configuration annotations of prototype methods in the interface * annotated with this annotation. Annotations that can be inherited are for example {@link Mapping}, diff --git a/core/src/main/java/org/mapstruct/Mapping.java b/core/src/main/java/org/mapstruct/Mapping.java index b6a4dc87e..66f4f825e 100644 --- a/core/src/main/java/org/mapstruct/Mapping.java +++ b/core/src/main/java/org/mapstruct/Mapping.java @@ -242,6 +242,7 @@ public @interface Mapping { * If not possible, MapStruct will try to apply a user defined mapping method. * * + *

* *

  • other *

    @@ -267,4 +268,18 @@ public @interface Mapping { */ NullValueCheckStrategy nullValueCheckStrategy() default ON_IMPLICIT_CONVERSION; + /** + * The strategy to be applied when the source property is {@code null} or not present. If no strategy is configured, + * the strategy given via {@link MapperConfig#nullValuePropertyMappingStrategy()}, + * {@link BeanMapping#nullValuePropertyMappingStrategy()} or + * {@link Mapper#nullValuePropertyMappingStrategy()} will be applied. + * + * {@link NullValuePropertyMappingStrategy#SET_TO_NULL} will be used by default. + * + * @return The strategy to be applied when {@code null} is passed as source property value or the source property + * is not present. + */ + NullValuePropertyMappingStrategy nullValuePropertyMappingStrategy() + default NullValuePropertyMappingStrategy.SET_TO_NULL; + } diff --git a/core/src/main/java/org/mapstruct/MappingConstants.java b/core/src/main/java/org/mapstruct/MappingConstants.java index f679d1157..8b0da9a72 100644 --- a/core/src/main/java/org/mapstruct/MappingConstants.java +++ b/core/src/main/java/org/mapstruct/MappingConstants.java @@ -16,17 +16,18 @@ public final class MappingConstants { } /** - * Represents a {@code null} source or target. + * In an {@link ValueMapping} this represents a {@code null} source or target. */ public static final String NULL = ""; /** - * Represents any source that is not already mapped by either a defined mapping or by means of name based mapping. + * In an {@link ValueMapping} this represents any source that is not already mapped by either a defined mapping or + * by means of name based mapping. */ public static final String ANY_REMAINING = ""; /** - * Represents any source that is not already mapped by a defined mapping. + * In an {@link ValueMapping} this represents any source that is not already mapped by a defined mapping. */ public static final String ANY_UNMAPPED = ""; diff --git a/core/src/main/java/org/mapstruct/NullValueCheckStrategy.java b/core/src/main/java/org/mapstruct/NullValueCheckStrategy.java index 8f31a35f7..8d5b53c00 100644 --- a/core/src/main/java/org/mapstruct/NullValueCheckStrategy.java +++ b/core/src/main/java/org/mapstruct/NullValueCheckStrategy.java @@ -33,6 +33,6 @@ public enum NullValueCheckStrategy { /** * This option always includes a null check. */ - ALWAYS, + ALWAYS; } diff --git a/core/src/main/java/org/mapstruct/NullValuePropertyMappingStrategy.java b/core/src/main/java/org/mapstruct/NullValuePropertyMappingStrategy.java new file mode 100644 index 000000000..41ee737d0 --- /dev/null +++ b/core/src/main/java/org/mapstruct/NullValuePropertyMappingStrategy.java @@ -0,0 +1,37 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct; + +/** + * Strategy for dealing with {@code null} or not present properties in the source bean. The + * {@link NullValuePropertyMappingStrategy} can be defined on {@link MapperConfig}, {@link Mapper}, {@link BeanMapping} + * and {@link Mapping}. + * Precedence is arranged in the reverse order. So {@link Mapping} will override {@link BeanMapping}, will + * overide {@link Mapper} + * + * The enum only applies to update method: methods that update a pre-existing target (annotated with + * {@code @}{@link MappingTarget}). + * + * @author Sjaak Derksen + */ +public enum NullValuePropertyMappingStrategy { + + /** + * If a source bean property equals {@code null} the target bean property will be set explicitly to {@code null}. + */ + SET_TO_NULL, + + /** + * If a source bean property equals {@code null} the target bean property will be set to its default value. + */ + SET_TO_DEFAULT, + + /** + * If a source bean property equals {@code null} the target bean property will be ignored and retain its + * existing value. + */ + IGNORE; +} diff --git a/documentation/src/main/asciidoc/mapstruct-reference-guide.asciidoc b/documentation/src/main/asciidoc/mapstruct-reference-guide.asciidoc index 50804de70..bf2e384bf 100644 --- a/documentation/src/main/asciidoc/mapstruct-reference-guide.asciidoc +++ b/documentation/src/main/asciidoc/mapstruct-reference-guide.asciidoc @@ -2107,6 +2107,20 @@ However, by specifying `nullValueMappingStrategy = NullValueMappingStrategy.RETU The strategy works in a hierarchical fashion. Setting `nullValueMappingStrategy` on mapping method level will override `@Mapper#nullValueMappingStrategy`, and `@Mapper#nullValueMappingStrategy` will override `@MappingConfig#nullValueMappingStrategy`. +[[mapping-result-for-null-properties]] +=== Controlling mapping result for 'null' properties in bean mappings (update mapping methods only). + +MapStruct offers control over the property to set in an `@MappingTarget` annotated target bean when the source property equals `null` or the presence check method yields absent. + +By default the source property will be set to null. However: + +1. By specifying `nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.SET_TO_DEFAULT` on `@Mapping`, `@BeanMapping`, `@Mapper` or `@MappingConfig`, the mapping result can be altered to return *default* values (`Object`, `ArrayList`, `HashMap`). + +2. By specifying `nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE` on `@Mapping`, `@BeanMapping`, `@Mapper` or `@MappingConfig`, the mapping result will be equal to the original value of the `@MappingTarget` annotated target. + +The strategy works in a hierarchical fashion. Setting `Mapping#nullValuePropertyMappingStrategy` on mapping level will override `nullValuePropertyMappingStrategy` on mapping method level will override `@Mapper#nullValuePropertyMappingStrategy`, and `@Mapper#nullValuePropertyMappingStrategy` will override `@MappingConfig#nullValuePropertyMappingStrategy`. + + [[checking-source-property-for-null-arguments]] === Controlling checking result for 'null' properties in bean mapping @@ -2122,7 +2136,7 @@ First calling a mapping method on the source property is not protected by a null The option `nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS` will always include a null check when source is non primitive, unless a source presence checker is defined on the source bean. -The strategy works in a hierarchical fashion. `@Mapper#nullValueCheckStrategy` will override `@MappingConfig#nullValueCheckStrategy`. +The strategy works in a hierarchical fashion. `@Mapping#nullValueCheckStrategy` will override `@BeanMapping#nullValueCheckStrategy`, `@BeanMapping#nullValueCheckStrategy` will override `@Mapper#nullValueCheckStrategy` and `@Mapper#nullValueCheckStrategy` will override `@MappingConfig#nullValueCheckStrategy`. [[source-presence-check]] === Source presence checking diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java index 1dff9c196..0cd484122 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java @@ -477,7 +477,8 @@ public class BeanMappingMethod extends NormalTypeMappingMethod { .dependsOn( mapping.getDependsOn() ) .defaultValue( mapping.getDefaultValue() ) .defaultJavaExpression( mapping.getDefaultJavaExpression() ) - .nullValueCheckStrategyPrism( mapping.getNullValueCheckStrategy() ) + .nullValueCheckStrategy( mapping.getNullValueCheckStrategy() ) + .nullValuePropertyMappingStrategy( mapping.getNullValuePropertyMappingStrategy() ) .build(); handledTargets.add( propertyName ); unprocessedSourceParameters.remove( sourceRef.getParameter() ); @@ -594,8 +595,9 @@ public class BeanMappingMethod extends NormalTypeMappingMethod { .existingVariableNames( existingVariableNames ) .dependsOn( mapping != null ? mapping.getDependsOn() : Collections.emptyList() ) .forgeMethodWithMappingOptions( extractAdditionalOptions( targetPropertyName, false ) ) - .nullValueCheckStrategyPrism( mapping != null ? mapping.getNullValueCheckStrategy() - : null ) + .nullValueCheckStrategy( mapping != null ? mapping.getNullValueCheckStrategy() : null ) + .nullValuePropertyMappingStrategy( mapping != null ? + mapping.getNullValuePropertyMappingStrategy() : null ) .build(); unprocessedSourceParameters.remove( sourceParameter ); @@ -659,7 +661,9 @@ public class BeanMappingMethod extends NormalTypeMappingMethod { .existingVariableNames( existingVariableNames ) .dependsOn( mapping != null ? mapping.getDependsOn() : Collections.emptyList() ) .forgeMethodWithMappingOptions( extractAdditionalOptions( targetProperty.getKey(), false ) ) - .nullValueCheckStrategyPrism( mapping != null ? mapping.getNullValueCheckStrategy() : null ) + .nullValueCheckStrategy( mapping != null ? mapping.getNullValueCheckStrategy() : null ) + .nullValuePropertyMappingStrategy( mapping != null ? + mapping.getNullValuePropertyMappingStrategy() : null ) .build(); propertyMappings.add( propertyMapping ); diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/CollectionAssignmentBuilder.java b/processor/src/main/java/org/mapstruct/ap/internal/model/CollectionAssignmentBuilder.java index 7649c373f..dcef911e8 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/CollectionAssignmentBuilder.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/CollectionAssignmentBuilder.java @@ -17,7 +17,7 @@ import org.mapstruct.ap.internal.model.source.Method; import org.mapstruct.ap.internal.model.source.SelectionParameters; import org.mapstruct.ap.internal.prism.CollectionMappingStrategyPrism; import org.mapstruct.ap.internal.prism.NullValueCheckStrategyPrism; -import org.mapstruct.ap.internal.prism.NullValueMappingStrategyPrism; +import org.mapstruct.ap.internal.prism.NullValuePropertyMappingStrategyPrism; import org.mapstruct.ap.internal.util.Message; import org.mapstruct.ap.internal.util.accessor.Accessor; @@ -57,7 +57,8 @@ public class CollectionAssignmentBuilder { private PropertyMapping.TargetWriteAccessorType targetAccessorType; private Assignment assignment; private SourceRHS sourceRHS; - private NullValueCheckStrategyPrism nullValueCheckStrategy; + private NullValueCheckStrategyPrism nvcs; + private NullValuePropertyMappingStrategyPrism nvpms; public CollectionAssignmentBuilder mappingBuilderContext(MappingBuilderContext ctx) { this.ctx = ctx; @@ -109,8 +110,13 @@ public class CollectionAssignmentBuilder { return this; } - public CollectionAssignmentBuilder nullValueCheckStrategy( NullValueCheckStrategyPrism nullValueCheckStrategy ) { - this.nullValueCheckStrategy = nullValueCheckStrategy; + public CollectionAssignmentBuilder nullValueCheckStrategy( NullValueCheckStrategyPrism nvcs ) { + this.nvcs = nvcs; + return this; + } + + public CollectionAssignmentBuilder nullValuePropertyMappingStrategy( NullValuePropertyMappingStrategyPrism nvpms ) { + this.nvpms = nvpms; return this; } @@ -143,7 +149,7 @@ public class CollectionAssignmentBuilder { PropertyMapping.TargetWriteAccessorType.isFieldAssignment( targetAccessorType ), targetType, true, - mapNullToDefault() + nvpms ); } else if ( method.isUpdateMethod() && !targetImmutable ) { @@ -152,22 +158,21 @@ public class CollectionAssignmentBuilder { result, method.getThrownTypes(), targetType, - nullValueCheckStrategy, + nvcs, + nvpms, ctx.getTypeFactory(), - PropertyMapping.TargetWriteAccessorType.isFieldAssignment( targetAccessorType ), - mapNullToDefault() + PropertyMapping.TargetWriteAccessorType.isFieldAssignment( targetAccessorType ) ); } else if ( result.getType() == Assignment.AssignmentType.DIRECT || - nullValueCheckStrategy == NullValueCheckStrategyPrism.ALWAYS ) { + nvcs == NullValueCheckStrategyPrism.ALWAYS ) { result = new SetterWrapperForCollectionsAndMapsWithNullCheck( result, method.getThrownTypes(), targetType, ctx.getTypeFactory(), - PropertyMapping.TargetWriteAccessorType.isFieldAssignment( targetAccessorType ), - mapNullToDefault() + PropertyMapping.TargetWriteAccessorType.isFieldAssignment( targetAccessorType ) ); } else { @@ -201,9 +206,4 @@ public class CollectionAssignmentBuilder { return result; } - private boolean mapNullToDefault() { - return method.getMapperConfiguration().getNullValueMappingStrategy() - == NullValueMappingStrategyPrism.RETURN_DEFAULT; - } - } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/PropertyMapping.java b/processor/src/main/java/org/mapstruct/ap/internal/model/PropertyMapping.java index 10cfbeb88..c8c0b00cd 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/PropertyMapping.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/PropertyMapping.java @@ -38,6 +38,7 @@ import org.mapstruct.ap.internal.model.source.SelectionParameters; import org.mapstruct.ap.internal.model.source.SourceReference; import org.mapstruct.ap.internal.prism.NullValueCheckStrategyPrism; import org.mapstruct.ap.internal.prism.NullValueMappingStrategyPrism; +import org.mapstruct.ap.internal.prism.NullValuePropertyMappingStrategyPrism; import org.mapstruct.ap.internal.util.AccessorNamingUtils; import org.mapstruct.ap.internal.util.Executables; import org.mapstruct.ap.internal.util.MapperConfiguration; @@ -197,7 +198,9 @@ public class PropertyMapping extends ModelElement { private MappingOptions forgeMethodWithMappingOptions; private boolean forceUpdateMethod; private boolean forgedNamedBased = true; - private NullValueCheckStrategyPrism nullValueCheckStrategyPrism; + private NullValueCheckStrategyPrism nvcs; + private NullValueMappingStrategyPrism nvms; + private NullValuePropertyMappingStrategyPrism nvpms; PropertyMappingBuilder() { super( PropertyMappingBuilder.class ); @@ -254,13 +257,33 @@ public class PropertyMapping extends ModelElement { return this; } - public PropertyMappingBuilder nullValueCheckStrategyPrism( - NullValueCheckStrategyPrism nullValueCheckStrategyPrism) { - this.nullValueCheckStrategyPrism = nullValueCheckStrategyPrism; + public PropertyMappingBuilder nullValueCheckStrategy(NullValueCheckStrategyPrism nvcs ) { + this.nvcs = nvcs; + return this; + } + + public PropertyMappingBuilder nullValuePropertyMappingStrategy( NullValuePropertyMappingStrategyPrism nvpms ) { + this.nvpms = nvpms; return this; } public PropertyMapping build() { + + MapperConfiguration mapperConfiguration = method.getMapperConfiguration(); + BeanMapping beanMapping = method.getMappingOptions().getBeanMapping(); + + // null value check strategy (determine true value based on hierarchy) + NullValueCheckStrategyPrism nvcsBean = beanMapping != null ? beanMapping.getNullValueCheckStrategy() : null; + this.nvcs = mapperConfiguration.getNullValueCheckStrategy( nvcsBean, nvcs ); + + // null value mapping strategy + this.nvms = mapperConfiguration.getNullValueMappingStrategy(); + + // null value property mapping strategy (determine true value based on hierarchy) + NullValuePropertyMappingStrategyPrism nvpmsBean = + beanMapping != null ? beanMapping.getNullValuePropertyMappingStrategy() : null; + this.nvpms = mapperConfiguration.getNullValuePropertyMappingStrategy( nvpmsBean, nvpms ); + // handle source this.rightHandSide = getSourceRHS( sourceReference ); rightHandSide.setUseElementAsSourceTypeForMatching( @@ -425,25 +448,22 @@ public class PropertyMapping extends ModelElement { ); } - boolean mapNullToDefault = method.getMapperConfiguration(). - getNullValueMappingStrategy() == NullValueMappingStrategyPrism.RETURN_DEFAULT; - Assignment factory = ObjectFactoryMethodResolver .getFactoryMethod( method, targetType, SelectionParameters.forSourceRHS( rightHandSide ), ctx ); - return new UpdateWrapper( rhs, method.getThrownTypes(), factory, isFieldAssignment(), targetType, - !rhs.isSourceReferenceParameter(), mapNullToDefault ); + return new UpdateWrapper( + rhs, + method.getThrownTypes(), + factory, isFieldAssignment(), + targetType, + !rhs.isSourceReferenceParameter(), + nvpms + ); } else { - 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) { Assignment result = rightHandSide; @@ -473,7 +493,8 @@ public class PropertyMapping extends ModelElement { .targetAccessorType( targetAccessorType ) .rightHandSide( rightHandSide ) .assignment( rhs ) - .nullValueCheckStrategy( getNvcs() ) + .nullValueCheckStrategy( nvcs ) + .nullValuePropertyMappingStrategy( nvpms ) .build(); } @@ -831,14 +852,17 @@ public class PropertyMapping extends ModelElement { ); } - boolean mapNullToDefault = method.getMapperConfiguration(). - getNullValueMappingStrategy() == NullValueMappingStrategyPrism.RETURN_DEFAULT; - Assignment factoryMethod = ObjectFactoryMethodResolver.getFactoryMethod( method, targetType, null, ctx ); - assignment = new UpdateWrapper( assignment, method.getThrownTypes(), factoryMethod, - isFieldAssignment(), targetType, false, mapNullToDefault ); + assignment = new UpdateWrapper( + assignment, + method.getThrownTypes(), + factoryMethod, + isFieldAssignment(), + targetType, + false, + null ); } else { assignment = new SetterWrapper( assignment, method.getThrownTypes(), isFieldAssignment() ); diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/ExistingInstanceSetterWrapperForCollectionsAndMaps.java b/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/ExistingInstanceSetterWrapperForCollectionsAndMaps.java index 01875b7e6..d567b2d28 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/ExistingInstanceSetterWrapperForCollectionsAndMaps.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/ExistingInstanceSetterWrapperForCollectionsAndMaps.java @@ -6,13 +6,18 @@ package org.mapstruct.ap.internal.model.assignment; import static org.mapstruct.ap.internal.prism.NullValueCheckStrategyPrism.ALWAYS; +import static org.mapstruct.ap.internal.prism.NullValuePropertyMappingStrategyPrism.IGNORE; +import static org.mapstruct.ap.internal.prism.NullValuePropertyMappingStrategyPrism.SET_TO_DEFAULT; +import java.util.HashSet; import java.util.List; +import java.util.Set; import org.mapstruct.ap.internal.model.common.Assignment; import org.mapstruct.ap.internal.model.common.Type; import org.mapstruct.ap.internal.model.common.TypeFactory; import org.mapstruct.ap.internal.prism.NullValueCheckStrategyPrism; +import org.mapstruct.ap.internal.prism.NullValuePropertyMappingStrategyPrism; /** * This wrapper handles the situation where an assignment is done for an update method. @@ -29,28 +34,45 @@ import org.mapstruct.ap.internal.prism.NullValueCheckStrategyPrism; public class ExistingInstanceSetterWrapperForCollectionsAndMaps extends SetterWrapperForCollectionsAndMapsWithNullCheck { - private final boolean includeSourceNullCheck; + private final boolean includeElseBranch; + private final boolean mapNullToDefault; + private final Type targetType; public ExistingInstanceSetterWrapperForCollectionsAndMaps(Assignment decoratedAssignment, List thrownTypesToExclude, Type targetType, - NullValueCheckStrategyPrism nvms, + NullValueCheckStrategyPrism nvcs, + NullValuePropertyMappingStrategyPrism nvpms, TypeFactory typeFactory, - boolean fieldAssignment, - boolean mapNullToDefault) { + boolean fieldAssignment) { super( decoratedAssignment, thrownTypesToExclude, targetType, typeFactory, - fieldAssignment, - mapNullToDefault + fieldAssignment ); - this.includeSourceNullCheck = ALWAYS == nvms; + this.mapNullToDefault = SET_TO_DEFAULT == nvpms; + this.targetType = targetType; + this.includeElseBranch = ALWAYS != nvcs && IGNORE != nvpms; } - public boolean isIncludeSourceNullCheck() { - return includeSourceNullCheck; + @Override + public Set getImportTypes() { + Set imported = new HashSet( super.getImportTypes() ); + if ( isMapNullToDefault() && ( targetType.getImplementationType() != null ) ) { + imported.add( targetType.getImplementationType() ); + } + return imported; } + + public boolean isIncludeElseBranch() { + return includeElseBranch; + } + + public boolean isMapNullToDefault() { + return mapNullToDefault; + } + } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/SetterWrapperForCollectionsAndMapsWithNullCheck.java b/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/SetterWrapperForCollectionsAndMapsWithNullCheck.java index 7746a7bed..5e3f4d63f 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/SetterWrapperForCollectionsAndMapsWithNullCheck.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/SetterWrapperForCollectionsAndMapsWithNullCheck.java @@ -26,14 +26,12 @@ public class SetterWrapperForCollectionsAndMapsWithNullCheck extends WrapperForC private final Type targetType; private final TypeFactory typeFactory; - private final boolean mapNullToDefault; public SetterWrapperForCollectionsAndMapsWithNullCheck(Assignment decoratedAssignment, List thrownTypesToExclude, Type targetType, TypeFactory typeFactory, - boolean fieldAssignment, - boolean mapNullToDefault) { + boolean fieldAssignment) { super( decoratedAssignment, thrownTypesToExclude, @@ -42,7 +40,6 @@ public class SetterWrapperForCollectionsAndMapsWithNullCheck extends WrapperForC ); this.targetType = targetType; this.typeFactory = typeFactory; - this.mapNullToDefault = mapNullToDefault; } @Override @@ -63,9 +60,6 @@ public class SetterWrapperForCollectionsAndMapsWithNullCheck extends WrapperForC if (isDirectAssignment() || getSourcePresenceCheckerReference() == null ) { imported.addAll( getNullCheckLocalVarType().getImportTypes() ); } - if ( isMapNullToDefault() && ( targetType.getImplementationType() != null ) ) { - imported.add( targetType.getImplementationType() ); - } return imported; } @@ -77,8 +71,4 @@ public class SetterWrapperForCollectionsAndMapsWithNullCheck extends WrapperForC return "java.util.EnumSet".equals( targetType.getFullyQualifiedName() ); } - public boolean isMapNullToDefault() { - return mapNullToDefault; - } - } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/UpdateWrapper.java b/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/UpdateWrapper.java index 4de30e32e..8176f6a8f 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/UpdateWrapper.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/UpdateWrapper.java @@ -12,6 +12,10 @@ import java.util.Set; import org.mapstruct.ap.internal.model.common.Assignment; import org.mapstruct.ap.internal.model.common.Type; +import org.mapstruct.ap.internal.prism.NullValuePropertyMappingStrategyPrism; + +import static org.mapstruct.ap.internal.prism.NullValuePropertyMappingStrategyPrism.SET_TO_DEFAULT; +import static org.mapstruct.ap.internal.prism.NullValuePropertyMappingStrategyPrism.SET_TO_NULL; /** * Wraps the assignment in a target setter. @@ -24,6 +28,7 @@ public class UpdateWrapper extends AssignmentWrapper { private final Assignment factoryMethod; private final Type targetImplementationType; private final boolean includeSourceNullCheck; + private final boolean includeExplicitNullWhenSourceIsNull; private final boolean mapNullToDefault; public UpdateWrapper( Assignment decoratedAssignment, @@ -32,13 +37,14 @@ public class UpdateWrapper extends AssignmentWrapper { boolean fieldAssignment, Type targetType, boolean includeSourceNullCheck, - boolean mapNullToDefault ) { + NullValuePropertyMappingStrategyPrism nvpms) { super( decoratedAssignment, fieldAssignment ); this.thrownTypesToExclude = thrownTypesToExclude; this.factoryMethod = factoryMethod; this.targetImplementationType = determineImplType( factoryMethod, targetType ); this.includeSourceNullCheck = includeSourceNullCheck; - this.mapNullToDefault = mapNullToDefault; + this.mapNullToDefault = nvpms == SET_TO_DEFAULT; + this.includeExplicitNullWhenSourceIsNull = nvpms == SET_TO_NULL; } private static Type determineImplType(Assignment factoryMethod, Type targetType) { @@ -91,6 +97,10 @@ public class UpdateWrapper extends AssignmentWrapper { return includeSourceNullCheck; } + public boolean isIncludeExplicitNullWhenSourceIsNull() { + return includeExplicitNullWhenSourceIsNull; + } + public boolean isMapNullToDefault() { return mapNullToDefault; } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/BeanMapping.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/BeanMapping.java index ce91d9acd..f2dc9527d 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/BeanMapping.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/BeanMapping.java @@ -15,6 +15,7 @@ import org.mapstruct.ap.internal.prism.BeanMappingPrism; 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.NullValuePropertyMappingStrategyPrism; import org.mapstruct.ap.internal.prism.ReportingPolicyPrism; import org.mapstruct.ap.internal.util.FormattingMessager; import org.mapstruct.ap.internal.util.Message; @@ -33,6 +34,7 @@ public class BeanMapping { private final boolean ignoreByDefault; private final List ignoreUnmappedSourceProperties; private final BuilderPrism builder; + private final NullValuePropertyMappingStrategyPrism nullValuePropertyMappingStrategy; /** * creates a mapping for inheritance. Will set ignoreByDefault to false. @@ -44,6 +46,7 @@ public class BeanMapping { return new BeanMapping( map.selectionParameters, map.nullValueMappingStrategy, + map.nullValuePropertyMappingStrategy, map.nullValueCheckStrategy, map.reportingPolicy, false, @@ -66,6 +69,11 @@ public class BeanMapping { ? null : NullValueMappingStrategyPrism.valueOf( beanMapping.nullValueMappingStrategy() ); + NullValuePropertyMappingStrategyPrism nullValuePropertyMappingStrategy = + null == beanMapping.values.nullValuePropertyMappingStrategy() + ? null + : NullValuePropertyMappingStrategyPrism.valueOf( beanMapping.nullValuePropertyMappingStrategy() ); + NullValueCheckStrategyPrism nullValueCheckStrategy = null == beanMapping.values.nullValueCheckStrategy() ? null @@ -79,8 +87,8 @@ public class BeanMapping { if ( !resultTypeIsDefined && beanMapping.qualifiedBy().isEmpty() && beanMapping.qualifiedByName().isEmpty() && beanMapping.ignoreUnmappedSourceProperties().isEmpty() - && ( nullValueMappingStrategy == null ) && ( nullValueCheckStrategy == null ) && !ignoreByDefault - && builderMapping == null ) { + && ( nullValueMappingStrategy == null ) && ( nullValuePropertyMappingStrategy == null ) + && ( nullValueCheckStrategy == null ) && !ignoreByDefault && builderMapping == null ) { messager.printMessage( method, Message.BEANMAPPING_NO_ELEMENTS ); } @@ -96,6 +104,7 @@ public class BeanMapping { return new BeanMapping( cmp, nullValueMappingStrategy, + nullValuePropertyMappingStrategy, nullValueCheckStrategy, null, ignoreByDefault, @@ -115,6 +124,7 @@ public class BeanMapping { null, null, null, + null, ReportingPolicyPrism.IGNORE, false, Collections.emptyList(), @@ -123,11 +133,12 @@ public class BeanMapping { } private BeanMapping(SelectionParameters selectionParameters, NullValueMappingStrategyPrism nvms, - NullValueCheckStrategyPrism nvcs, + NullValuePropertyMappingStrategyPrism nvpms, NullValueCheckStrategyPrism nvcs, ReportingPolicyPrism reportingPolicy, boolean ignoreByDefault, List ignoreUnmappedSourceProperties, BuilderPrism builder) { this.selectionParameters = selectionParameters; this.nullValueMappingStrategy = nvms; + this.nullValuePropertyMappingStrategy = nvpms; this.nullValueCheckStrategy = nvcs; this.reportingPolicy = reportingPolicy; this.ignoreByDefault = ignoreByDefault; @@ -143,6 +154,10 @@ public class BeanMapping { return nullValueMappingStrategy; } + public NullValuePropertyMappingStrategyPrism getNullValuePropertyMappingStrategy() { + return nullValuePropertyMappingStrategy; + } + public NullValueCheckStrategyPrism getNullValueCheckStrategy() { return nullValueCheckStrategy; } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/Mapping.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/Mapping.java index aed9855b5..f9bd4d4a3 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/Mapping.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/Mapping.java @@ -27,6 +27,7 @@ import org.mapstruct.ap.internal.model.common.TypeFactory; import org.mapstruct.ap.internal.prism.MappingPrism; import org.mapstruct.ap.internal.prism.MappingsPrism; import org.mapstruct.ap.internal.prism.NullValueCheckStrategyPrism; +import org.mapstruct.ap.internal.prism.NullValuePropertyMappingStrategyPrism; import org.mapstruct.ap.internal.util.AccessorNamingUtils; import org.mapstruct.ap.internal.util.FormattingMessager; import org.mapstruct.ap.internal.util.Message; @@ -58,6 +59,7 @@ public class Mapping { private final AnnotationValue targetAnnotationValue; private final AnnotationValue dependsOnAnnotationValue; private final NullValueCheckStrategyPrism nullValueCheckStrategy; + private final NullValuePropertyMappingStrategyPrism nullValuePropertyMappingStrategy; private SourceReference sourceReference; private TargetReference targetReference; @@ -89,70 +91,7 @@ public class Mapping { public static Mapping fromMappingPrism(MappingPrism mappingPrism, ExecutableElement element, FormattingMessager messager, Types typeUtils) { - if ( mappingPrism.target().isEmpty() ) { - messager.printMessage( - element, - mappingPrism.mirror, - mappingPrism.values.target(), - Message.PROPERTYMAPPING_EMPTY_TARGET - ); - return null; - } - - if ( !mappingPrism.source().isEmpty() && mappingPrism.values.constant() != null ) { - messager.printMessage( - element, - mappingPrism.mirror, - Message.PROPERTYMAPPING_SOURCE_AND_CONSTANT_BOTH_DEFINED ); - return null; - } - else if ( !mappingPrism.source().isEmpty() && mappingPrism.values.expression() != null ) { - messager.printMessage( - element, - mappingPrism.mirror, - Message.PROPERTYMAPPING_SOURCE_AND_EXPRESSION_BOTH_DEFINED ); - return null; - } - else if ( mappingPrism.values.expression() != null && mappingPrism.values.constant() != null ) { - messager.printMessage( - element, - mappingPrism.mirror, - Message.PROPERTYMAPPING_EXPRESSION_AND_CONSTANT_BOTH_DEFINED ); - return null; - } - else if ( mappingPrism.values.expression() != null && mappingPrism.values.defaultValue() != null ) { - messager.printMessage( - element, - mappingPrism.mirror, - Message.PROPERTYMAPPING_EXPRESSION_AND_DEFAULT_VALUE_BOTH_DEFINED ); - return null; - } - else if ( mappingPrism.values.constant() != null && mappingPrism.values.defaultValue() != null ) { - messager.printMessage( - element, - mappingPrism.mirror, - Message.PROPERTYMAPPING_CONSTANT_AND_DEFAULT_VALUE_BOTH_DEFINED ); - return null; - } - else if ( mappingPrism.values.expression() != null && mappingPrism.values.defaultExpression() != null) { - messager.printMessage( - element, - mappingPrism.mirror, - Message.PROPERTYMAPPING_EXPRESSION_AND_DEFAULT_EXPRESSION_BOTH_DEFINED ); - return null; - } - else if ( mappingPrism.values.constant() != null && mappingPrism.values.defaultExpression() != null) { - messager.printMessage( - element, - mappingPrism.mirror, - Message.PROPERTYMAPPING_CONSTANT_AND_DEFAULT_EXPRESSION_BOTH_DEFINED ); - return null; - } - else if ( mappingPrism.values.defaultValue() != null && mappingPrism.values.defaultExpression() != null) { - messager.printMessage( - element, - mappingPrism.mirror, - Message.PROPERTYMAPPING_DEFAULT_VALUE_AND_DEFAULT_EXPRESSION_BOTH_DEFINED ); + if (!isConsistent( mappingPrism, element, messager ) ) { return null; } @@ -168,7 +107,6 @@ public class Mapping { List dependsOn = mappingPrism.dependsOn() != null ? mappingPrism.dependsOn() : Collections.emptyList(); - FormattingParameters formattingParam = new FormattingParameters( dateFormat, numberFormat, @@ -188,6 +126,11 @@ public class Mapping { ? null : NullValueCheckStrategyPrism.valueOf( mappingPrism.nullValueCheckStrategy() ); + NullValuePropertyMappingStrategyPrism nullValuePropertyMappingStrategy = + null == mappingPrism.values.nullValuePropertyMappingStrategy() + ? null + : NullValuePropertyMappingStrategyPrism.valueOf( mappingPrism.nullValuePropertyMappingStrategy() ); + return new Mapping( source, constant, @@ -203,7 +146,8 @@ public class Mapping { selectionParams, mappingPrism.values.dependsOn(), dependsOn, - nullValueCheckStrategy + nullValueCheckStrategy, + nullValuePropertyMappingStrategy ); } @@ -223,17 +167,144 @@ public class Mapping { null, null, new ArrayList(), + null, null ); } + private static boolean isConsistent(MappingPrism mappingPrism, ExecutableElement element, + FormattingMessager messager) { + + if ( mappingPrism.target().isEmpty() ) { + messager.printMessage( + element, + mappingPrism.mirror, + mappingPrism.values.target(), + Message.PROPERTYMAPPING_EMPTY_TARGET + ); + return false; + } + + if ( !mappingPrism.source().isEmpty() && mappingPrism.values.constant() != null ) { + messager.printMessage( + element, + mappingPrism.mirror, + Message.PROPERTYMAPPING_SOURCE_AND_CONSTANT_BOTH_DEFINED + ); + return false; + } + else if ( !mappingPrism.source().isEmpty() && mappingPrism.values.expression() != null ) { + messager.printMessage( + element, + mappingPrism.mirror, + Message.PROPERTYMAPPING_SOURCE_AND_EXPRESSION_BOTH_DEFINED + ); + return false; + } + else if ( mappingPrism.values.expression() != null && mappingPrism.values.constant() != null ) { + messager.printMessage( + element, + mappingPrism.mirror, + Message.PROPERTYMAPPING_EXPRESSION_AND_CONSTANT_BOTH_DEFINED + ); + return false; + } + else if ( mappingPrism.values.expression() != null && mappingPrism.values.defaultValue() != null ) { + messager.printMessage( + element, + mappingPrism.mirror, + Message.PROPERTYMAPPING_EXPRESSION_AND_DEFAULT_VALUE_BOTH_DEFINED + ); + return false; + } + else if ( mappingPrism.values.constant() != null && mappingPrism.values.defaultValue() != null ) { + messager.printMessage( + element, + mappingPrism.mirror, + Message.PROPERTYMAPPING_CONSTANT_AND_DEFAULT_VALUE_BOTH_DEFINED + ); + return false; + } + else if ( mappingPrism.values.expression() != null && mappingPrism.values.defaultExpression() != null ) { + messager.printMessage( + element, + mappingPrism.mirror, + Message.PROPERTYMAPPING_EXPRESSION_AND_DEFAULT_EXPRESSION_BOTH_DEFINED + ); + return false; + } + else if ( mappingPrism.values.constant() != null && mappingPrism.values.defaultExpression() != null ) { + messager.printMessage( + element, + mappingPrism.mirror, + Message.PROPERTYMAPPING_CONSTANT_AND_DEFAULT_EXPRESSION_BOTH_DEFINED + ); + return false; + } + else if ( mappingPrism.values.defaultValue() != null && mappingPrism.values.defaultExpression() != null ) { + messager.printMessage( + element, + mappingPrism.mirror, + Message.PROPERTYMAPPING_DEFAULT_VALUE_AND_DEFAULT_EXPRESSION_BOTH_DEFINED + ); + return false; + } + else if ( mappingPrism.values.nullValuePropertyMappingStrategy() != null + && mappingPrism.values.defaultValue() != null ) { + messager.printMessage( + element, + mappingPrism.mirror, + Message.PROPERTYMAPPING_DEFAULT_VALUE_AND_NVPMS + ); + return false; + } + else if ( mappingPrism.values.nullValuePropertyMappingStrategy() != null + && mappingPrism.values.constant() != null ) { + messager.printMessage( + element, + mappingPrism.mirror, + Message.PROPERTYMAPPING_CONSTANT_VALUE_AND_NVPMS + ); + return false; + } + else if ( mappingPrism.values.nullValuePropertyMappingStrategy() != null + && mappingPrism.values.expression() != null ) { + messager.printMessage( + element, + mappingPrism.mirror, + Message.PROPERTYMAPPING_EXPRESSION_VALUE_AND_NVPMS + ); + return false; + } + else if ( mappingPrism.values.nullValuePropertyMappingStrategy() != null + && mappingPrism.values.defaultExpression() != null ) { + messager.printMessage( + element, + mappingPrism.mirror, + Message.PROPERTYMAPPING_DEFAULT_EXPERSSION_AND_NVPMS + ); + return false; + } + else if ( mappingPrism.values.nullValuePropertyMappingStrategy() != null + && mappingPrism.ignore() != null && mappingPrism.ignore() ) { + messager.printMessage( + element, + mappingPrism.mirror, + Message.PROPERTYMAPPING_IGNORE_AND_NVPMS + ); + return false; + } + return true; + } + @SuppressWarnings("checkstyle:parameternumber") private Mapping( String sourceName, String constant, String javaExpression, String defaultJavaExpression, String targetName, String defaultValue, boolean isIgnored, AnnotationMirror mirror, AnnotationValue sourceAnnotationValue, AnnotationValue targetAnnotationValue, FormattingParameters formattingParameters, SelectionParameters selectionParameters, AnnotationValue dependsOnAnnotationValue, List dependsOn, - NullValueCheckStrategyPrism nullValueCheckStrategy ) { + NullValueCheckStrategyPrism nullValueCheckStrategy, + NullValuePropertyMappingStrategyPrism nullValuePropertyMappingStrategy ) { this.sourceName = sourceName; this.constant = constant; this.javaExpression = javaExpression; @@ -249,6 +320,7 @@ public class Mapping { this.dependsOnAnnotationValue = dependsOnAnnotationValue; this.dependsOn = dependsOn; this.nullValueCheckStrategy = nullValueCheckStrategy; + this.nullValuePropertyMappingStrategy = nullValuePropertyMappingStrategy; } private Mapping( Mapping mapping, TargetReference targetReference ) { @@ -269,6 +341,7 @@ public class Mapping { this.sourceReference = mapping.sourceReference; this.targetReference = targetReference; this.nullValueCheckStrategy = mapping.nullValueCheckStrategy; + this.nullValuePropertyMappingStrategy = mapping.nullValuePropertyMappingStrategy; } private Mapping( Mapping mapping, SourceReference sourceReference ) { @@ -289,6 +362,7 @@ public class Mapping { this.sourceReference = sourceReference; this.targetReference = mapping.targetReference; this.nullValueCheckStrategy = mapping.nullValueCheckStrategy; + this.nullValuePropertyMappingStrategy = mapping.nullValuePropertyMappingStrategy; } private static String getExpression(MappingPrism mappingPrism, ExecutableElement element, @@ -458,6 +532,10 @@ public class Mapping { return nullValueCheckStrategy; } + public NullValuePropertyMappingStrategyPrism getNullValuePropertyMappingStrategy() { + return nullValuePropertyMappingStrategy; + } + public Mapping popTargetReference() { if ( targetReference != null ) { TargetReference newTargetReference = targetReference.pop(); @@ -506,7 +584,8 @@ public class Mapping { selectionParameters, dependsOnAnnotationValue, Collections.emptyList(), - nullValueCheckStrategy + nullValueCheckStrategy, + nullValuePropertyMappingStrategy ); reverse.init( @@ -548,7 +627,8 @@ public class Mapping { selectionParameters, dependsOnAnnotationValue, dependsOn, - nullValueCheckStrategy + nullValueCheckStrategy, + nullValuePropertyMappingStrategy ); if ( sourceReference != null ) { @@ -568,5 +648,6 @@ public class Mapping { "\n targetName='" + targetName + "\'," + "\n}"; } + } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/prism/MappingConstantsPrism.java b/processor/src/main/java/org/mapstruct/ap/internal/prism/MappingConstantsPrism.java index 959f571bb..c03bac6a7 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/prism/MappingConstantsPrism.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/prism/MappingConstantsPrism.java @@ -20,5 +20,4 @@ public final class MappingConstantsPrism { public static final String ANY_REMAINING = ""; public static final String ANY_UNMAPPED = ""; - } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/prism/NullValuePropertyMappingStrategyPrism.java b/processor/src/main/java/org/mapstruct/ap/internal/prism/NullValuePropertyMappingStrategyPrism.java new file mode 100644 index 000000000..bc0cdae9e --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/prism/NullValuePropertyMappingStrategyPrism.java @@ -0,0 +1,19 @@ +/* + * 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.internal.prism; + + +/** + * Prism for the enum {@link org.mapstruct.NullValuePropertyMappingStrategy} + * + * @author Sjaak Derksen + */ +public enum NullValuePropertyMappingStrategyPrism { + + SET_TO_NULL, + SET_TO_DEFAULT, + IGNORE; +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/MapperConfiguration.java b/processor/src/main/java/org/mapstruct/ap/internal/util/MapperConfiguration.java index d92d0c17e..49b0aded8 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/MapperConfiguration.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/MapperConfiguration.java @@ -22,6 +22,7 @@ import org.mapstruct.ap.internal.prism.MapperPrism; import org.mapstruct.ap.internal.prism.MappingInheritanceStrategyPrism; import org.mapstruct.ap.internal.prism.NullValueCheckStrategyPrism; import org.mapstruct.ap.internal.prism.NullValueMappingStrategyPrism; +import org.mapstruct.ap.internal.prism.NullValuePropertyMappingStrategyPrism; import org.mapstruct.ap.internal.prism.ReportingPolicyPrism; /** @@ -163,6 +164,25 @@ public class MapperConfiguration { } } + public NullValuePropertyMappingStrategyPrism getNullValuePropertyMappingStrategy( + NullValuePropertyMappingStrategyPrism beanPrism, + NullValuePropertyMappingStrategyPrism mappingPrism) { + if ( mappingPrism != null ) { + return mappingPrism; + } + else if ( beanPrism != null ) { + return beanPrism; + } + else if ( mapperConfigPrism != null && mapperPrism.values.nullValueCheckStrategy() == null ) { + return NullValuePropertyMappingStrategyPrism.valueOf( + mapperConfigPrism.nullValuePropertyMappingStrategy() + ); + } + else { + return NullValuePropertyMappingStrategyPrism.valueOf( mapperPrism.nullValuePropertyMappingStrategy() ); + } + } + public InjectionStrategyPrism getInjectionStrategy() { if ( mapperConfigPrism != null && mapperPrism.values.injectionStrategy() == null ) { return InjectionStrategyPrism.valueOf( mapperConfigPrism.injectionStrategy() ); diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/Message.java b/processor/src/main/java/org/mapstruct/ap/internal/util/Message.java index 155ea39a5..445c0b3b0 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/Message.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/Message.java @@ -15,7 +15,7 @@ import javax.tools.Diagnostic; public enum Message { // CHECKSTYLE:OFF - BEANMAPPING_NO_ELEMENTS( "'nullValueMappingStrategy', 'resultType' and 'qualifiedBy' are undefined in @BeanMapping, define at least one of them." ), + BEANMAPPING_NO_ELEMENTS( "'nullValueMappingStrategy', 'nullValuePropertyMappingStrategy', 'resultType' and 'qualifiedBy' are undefined in @BeanMapping, define at least one of them." ), BEANMAPPING_NOT_ASSIGNABLE( "%s not assignable to: %s." ), BEANMAPPING_ABSTRACT( "The result type %s may not be an abstract class nor interface." ), BEANMAPPING_UNKNOWN_PROPERTY_IN_RESULTTYPE( "Unknown property \"%s\" in result type %s. Did you mean \"%s\"?" ), @@ -44,6 +44,11 @@ public enum Message { PROPERTYMAPPING_EXPRESSION_AND_DEFAULT_EXPRESSION_BOTH_DEFINED( "Expression and default expression are both defined in @Mapping, either define an expression or a default expression." ), PROPERTYMAPPING_CONSTANT_AND_DEFAULT_EXPRESSION_BOTH_DEFINED( "Constant and default expression are both defined in @Mapping, either define a constant or a default expression." ), PROPERTYMAPPING_DEFAULT_VALUE_AND_DEFAULT_EXPRESSION_BOTH_DEFINED( "Default value and default expression are both defined in @Mapping, either define a default value or a default expression." ), + PROPERTYMAPPING_DEFAULT_VALUE_AND_NVPMS( "Default value and nullValuePropertyMappingStrategy are both defined in @Mapping, either define a defaultValue or an nullValuePropertyMappingStrategy." ), + PROPERTYMAPPING_EXPRESSION_VALUE_AND_NVPMS( "Expression and nullValuePropertyMappingStrategy are both defined in @Mapping, either define an expression or an nullValuePropertyMappingStrategy." ), + PROPERTYMAPPING_CONSTANT_VALUE_AND_NVPMS( "Constant and nullValuePropertyMappingStrategy are both defined in @Mapping, either define a constant or an nullValuePropertyMappingStrategy." ), + PROPERTYMAPPING_DEFAULT_EXPERSSION_AND_NVPMS( "DefaultExpression and nullValuePropertyMappingStrategy are both defined in @Mapping, either define a defaultExpression or an nullValuePropertyMappingStrategy." ), + PROPERTYMAPPING_IGNORE_AND_NVPMS( "Ignore and nullValuePropertyMappingStrategy are both defined in @Mapping, either define ignore or an nullValuePropertyMappingStrategy." ), PROPERTYMAPPING_INVALID_EXPRESSION( "Value for expression must be given in the form \"java()\"." ), PROPERTYMAPPING_INVALID_DEFAULT_EXPRESSION( "Value for default expression must be given in the form \"java()\"." ), PROPERTYMAPPING_INVALID_PARAMETER_NAME( "Method has no source parameter named \"%s\". Method source parameters are: \"%s\"." ), diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/ExistingInstanceSetterWrapperForCollectionsAndMaps.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/ExistingInstanceSetterWrapperForCollectionsAndMaps.ftl index 17eb9de51..cee722e2d 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/ExistingInstanceSetterWrapperForCollectionsAndMaps.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/ExistingInstanceSetterWrapperForCollectionsAndMaps.ftl @@ -14,7 +14,7 @@ ${ext.targetBeanName}.${ext.targetReadAccessorName}.clear(); ${ext.targetBeanName}.${ext.targetReadAccessorName}.<#if ext.targetType.collectionType>addAll<#else>putAll( <@lib.handleWithAssignmentOrNullCheckVar/> ); - <#if !ext.defaultValueAssignment?? && !sourcePresenceCheckerReference?? && !includeSourceNullCheck>else {<#-- the opposite (defaultValueAssignment) case is handeld inside lib.handleLocalVarNullCheck --> + <#if !ext.defaultValueAssignment?? && !sourcePresenceCheckerReference?? && includeElseBranch>else {<#-- the opposite (defaultValueAssignment) case is handeld inside lib.handleLocalVarNullCheck --> ${ext.targetBeanName}.${ext.targetWriteAccessorName}<@lib.handleWrite><#if mapNullToDefault><@lib.initTargetObject/><#else>null; } diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/SetterWrapperForCollectionsAndMapsWithNullCheck.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/SetterWrapperForCollectionsAndMapsWithNullCheck.ftl index 6e38d7fbf..c26557af9 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/SetterWrapperForCollectionsAndMapsWithNullCheck.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/SetterWrapperForCollectionsAndMapsWithNullCheck.ftl @@ -10,10 +10,6 @@ <@lib.sourceLocalVarAssignment/> <@lib.handleExceptions> <@callTargetWriteAccessor/> - <#if !ext.defaultValueAssignment??>else {<#-- the opposite (defaultValueAssignment) case is handeld inside lib.handleLocalVarNullCheck --> - ${ext.targetBeanName}.${ext.targetWriteAccessorName}<@lib.handleWrite><#if mapNullToDefault><@lib.initTargetObject/><#else>null; - } - <#-- assigns the target via the regular target write accessor (usually the setter) diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/UpdateWrapper.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/UpdateWrapper.ftl index 833120b01..9dfbc0858 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/UpdateWrapper.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/UpdateWrapper.ftl @@ -13,9 +13,11 @@ <@assignToExistingTarget/> <@lib.handleAssignment/>; } + <#if mapNullToDefault || includeExplicitNullWhenSourceIsNull> else { ${ext.targetBeanName}.${ext.targetWriteAccessorName}<@lib.handleWrite><#if mapNullToDefault><@lib.initTargetObject/><#else>null; } + <#else> <@assignToExistingTarget/> <@lib.handleAssignment/>; diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1273/Dto.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1273/Dto.java index d91aaa827..9c912c221 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1273/Dto.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1273/Dto.java @@ -5,12 +5,11 @@ */ package org.mapstruct.ap.test.bugs._1273; -import java.util.ArrayList; import java.util.List; public class Dto { - List longs = new ArrayList(); + List longs; public List getLongs() { return longs; diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1273/EntityMapperReturnDefault.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1273/EntityMapperReturnDefault.java index 3f0d1e003..0b31e1d73 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1273/EntityMapperReturnDefault.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1273/EntityMapperReturnDefault.java @@ -5,12 +5,20 @@ */ package org.mapstruct.ap.test.bugs._1273; -import org.mapstruct.Mapper; -import org.mapstruct.NullValueMappingStrategy; +import java.util.ArrayList; -@Mapper( nullValueMappingStrategy = NullValueMappingStrategy.RETURN_DEFAULT ) +import org.mapstruct.Mapper; +import org.mapstruct.ObjectFactory; + +@Mapper public interface EntityMapperReturnDefault { Dto asTarget(Entity entity); + @ObjectFactory + default Dto createDto() { + Dto result = new Dto(); + result.setLongs( new ArrayList<>( ) ); + return result; + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_913/DomainDtoWithNcvsAlwaysMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_913/DomainDtoWithNcvsAlwaysMapper.java index f7a995a35..11be51583 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_913/DomainDtoWithNcvsAlwaysMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_913/DomainDtoWithNcvsAlwaysMapper.java @@ -5,12 +5,15 @@ */ package org.mapstruct.ap.test.bugs._913; +import org.mapstruct.BeanMapping; import org.mapstruct.InheritConfiguration; import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.MappingTarget; import org.mapstruct.Mappings; +import org.mapstruct.Named; import org.mapstruct.NullValueCheckStrategy; +import org.mapstruct.ObjectFactory; import org.mapstruct.factory.Mappers; /** @@ -22,6 +25,7 @@ public interface DomainDtoWithNcvsAlwaysMapper { DomainDtoWithNcvsAlwaysMapper INSTANCE = Mappers.getMapper( DomainDtoWithNcvsAlwaysMapper.class ); + @BeanMapping( qualifiedByName = "DomainObjectFactory" ) @Mappings({ @Mapping(target = "strings", source = "strings"), @Mapping(target = "longs", source = "strings"), @@ -36,4 +40,13 @@ public interface DomainDtoWithNcvsAlwaysMapper { @InheritConfiguration( name = "create" ) Domain updateWithReturn(DtoWithPresenceCheck source, @MappingTarget Domain target); + + @ObjectFactory + @Named( "DomainObjectFactory" ) + default Domain createNullDomain() { + Domain domain = new Domain(); + domain.setLongs( null ); + domain.setStrings( null ); + return domain; + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_913/DomainDtoWithNvmsDefaultMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_913/DomainDtoWithNvmsDefaultMapper.java index 7fef9d8b9..7cd3cfede 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_913/DomainDtoWithNvmsDefaultMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_913/DomainDtoWithNvmsDefaultMapper.java @@ -11,13 +11,17 @@ import org.mapstruct.Mapping; import org.mapstruct.MappingTarget; import org.mapstruct.Mappings; import org.mapstruct.NullValueMappingStrategy; +import org.mapstruct.NullValuePropertyMappingStrategy; import org.mapstruct.factory.Mappers; /** * * @author Sjaak Derksen */ -@Mapper( nullValueMappingStrategy = NullValueMappingStrategy.RETURN_DEFAULT, uses = Helper.class ) +@Mapper( + nullValueMappingStrategy = NullValueMappingStrategy.RETURN_DEFAULT, + nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.SET_TO_DEFAULT, + uses = Helper.class ) public interface DomainDtoWithNvmsDefaultMapper { DomainDtoWithNvmsDefaultMapper INSTANCE = Mappers.getMapper( DomainDtoWithNvmsDefaultMapper.class ); diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_913/DomainDtoWithNvmsNullMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_913/DomainDtoWithNvmsNullMapper.java index 4d6fd9eff..d288b666f 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_913/DomainDtoWithNvmsNullMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_913/DomainDtoWithNvmsNullMapper.java @@ -5,11 +5,14 @@ */ package org.mapstruct.ap.test.bugs._913; +import org.mapstruct.BeanMapping; import org.mapstruct.InheritConfiguration; import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.MappingTarget; import org.mapstruct.Mappings; +import org.mapstruct.Named; +import org.mapstruct.ObjectFactory; import org.mapstruct.factory.Mappers; /** @@ -22,6 +25,7 @@ public interface DomainDtoWithNvmsNullMapper { DomainDtoWithNvmsNullMapper INSTANCE = Mappers.getMapper( DomainDtoWithNvmsNullMapper.class ); + @BeanMapping( qualifiedByName = "DomainObjectFactory" ) @Mappings({ @Mapping(target = "strings", source = "strings"), @Mapping(target = "longs", source = "strings"), @@ -36,4 +40,13 @@ public interface DomainDtoWithNvmsNullMapper { @InheritConfiguration( name = "create" ) Domain updateWithReturn(Dto source, @MappingTarget Domain target); + + @ObjectFactory + @Named( "DomainObjectFactory" ) + default Domain createNullDomain() { + Domain domain = new Domain(); + domain.setLongs( null ); + domain.setStrings( null ); + return domain; + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_913/DomainDtoWithPresenceCheckMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_913/DomainDtoWithPresenceCheckMapper.java index ddb0a60ce..83b8539a4 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_913/DomainDtoWithPresenceCheckMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_913/DomainDtoWithPresenceCheckMapper.java @@ -5,11 +5,14 @@ */ package org.mapstruct.ap.test.bugs._913; +import org.mapstruct.BeanMapping; import org.mapstruct.InheritConfiguration; import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.MappingTarget; import org.mapstruct.Mappings; +import org.mapstruct.Named; +import org.mapstruct.ObjectFactory; import org.mapstruct.factory.Mappers; /** @@ -22,6 +25,7 @@ public interface DomainDtoWithPresenceCheckMapper { DomainDtoWithPresenceCheckMapper INSTANCE = Mappers.getMapper( DomainDtoWithPresenceCheckMapper.class ); + @BeanMapping( qualifiedByName = "DomainObjectFactory" ) @Mappings({ @Mapping(target = "strings", source = "strings"), @Mapping(target = "longs", source = "strings"), @@ -36,4 +40,13 @@ public interface DomainDtoWithPresenceCheckMapper { @InheritConfiguration( name = "create" ) Domain updateWithReturn(DtoWithPresenceCheck source, @MappingTarget Domain target); + + @ObjectFactory + @Named( "DomainObjectFactory" ) + default Domain createNullDomain() { + Domain domain = new Domain(); + domain.setLongs( null ); + domain.setStrings( null ); + return domain; + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/Address.java b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/Address.java new file mode 100644 index 000000000..86c76f4d5 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/Address.java @@ -0,0 +1,19 @@ +/* + * 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.nullvaluepropertymapping; + +public class Address { + + private Integer houseNumber; + + public Integer getHouseNumber() { + return houseNumber; + } + + public void setHouseNumber(Integer houseNumber) { + this.houseNumber = houseNumber; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/AddressDTO.java b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/AddressDTO.java new file mode 100644 index 000000000..3fca3b046 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/AddressDTO.java @@ -0,0 +1,19 @@ +/* + * 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.nullvaluepropertymapping; + +public class AddressDTO { + + private Integer houseNo; + + public Integer getHouseNo() { + return houseNo; + } + + public void setHouseNo(Integer houseNo) { + this.houseNo = houseNo; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/Customer.java b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/Customer.java new file mode 100644 index 000000000..ac750aeb5 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/Customer.java @@ -0,0 +1,30 @@ +/* + * 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.nullvaluepropertymapping; + +import java.util.List; + +public class Customer { + + private Address address; + private List details; + + public Address getAddress() { + return address; + } + + public void setAddress(Address address) { + this.address = address; + } + + public List getDetails() { + return details; + } + + public void setDetails(List details) { + this.details = details; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/CustomerDTO.java b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/CustomerDTO.java new file mode 100644 index 000000000..b2475cd4c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/CustomerDTO.java @@ -0,0 +1,30 @@ +/* + * 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.nullvaluepropertymapping; + +import java.util.List; + +public class CustomerDTO { + + private AddressDTO address; + private List details; + + public AddressDTO getAddress() { + return address; + } + + public void setAddress(AddressDTO address) { + this.address = address; + } + + public List getDetails() { + return details; + } + + public void setDetails(List details) { + this.details = details; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/CustomerDefaultMapper.java b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/CustomerDefaultMapper.java new file mode 100644 index 000000000..019327ede --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/CustomerDefaultMapper.java @@ -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.nullvaluepropertymapping; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingTarget; +import org.mapstruct.NullValuePropertyMappingStrategy; +import org.mapstruct.factory.Mappers; + +@Mapper(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.SET_TO_DEFAULT) +public interface CustomerDefaultMapper { + + CustomerDefaultMapper INSTANCE = Mappers.getMapper( CustomerDefaultMapper.class ); + + @Mapping(source = "address", target = "homeDTO.addressDTO") + void mapCustomer(Customer customer, @MappingTarget UserDTO userDTO); + + @Mapping(source = "houseNumber", target = "houseNo") + void mapCustomerHouse(Address address, @MappingTarget AddressDTO addrDTO); + +} + diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/CustomerMapper.java b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/CustomerMapper.java new file mode 100644 index 000000000..56b930c51 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/CustomerMapper.java @@ -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.nullvaluepropertymapping; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingTarget; +import org.mapstruct.NullValuePropertyMappingStrategy; +import org.mapstruct.factory.Mappers; + +@Mapper(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE) +public interface CustomerMapper { + + CustomerMapper INSTANCE = Mappers.getMapper( CustomerMapper.class ); + + @Mapping(source = "address", target = "homeDTO.addressDTO") + void mapCustomer(Customer customer, @MappingTarget UserDTO userDTO); + + @Mapping(source = "houseNumber", target = "houseNo") + void mapCustomerHouse(Address address, @MappingTarget AddressDTO addrDTO); + +} + diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/CustomerNvpmsOnBeanMappingMethodMapper.java b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/CustomerNvpmsOnBeanMappingMethodMapper.java new file mode 100644 index 000000000..780108463 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/CustomerNvpmsOnBeanMappingMethodMapper.java @@ -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.nullvaluepropertymapping; + +import org.mapstruct.BeanMapping; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingTarget; +import org.mapstruct.NullValuePropertyMappingStrategy; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface CustomerNvpmsOnBeanMappingMethodMapper { + + CustomerNvpmsOnBeanMappingMethodMapper INSTANCE = Mappers.getMapper( CustomerNvpmsOnBeanMappingMethodMapper.class ); + + @BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE) + void map(Customer customer, @MappingTarget CustomerDTO mappingTarget); + + @BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE) + @Mapping(source = "houseNumber", target = "houseNo") + void mapCustomerHouse(Address address, @MappingTarget AddressDTO addrDTO); + +} + diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/CustomerNvpmsOnConfigMapper.java b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/CustomerNvpmsOnConfigMapper.java new file mode 100644 index 000000000..941b7d2b4 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/CustomerNvpmsOnConfigMapper.java @@ -0,0 +1,24 @@ +/* + * 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.nullvaluepropertymapping; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingTarget; +import org.mapstruct.factory.Mappers; + +@Mapper(config = NvpmsConfig.class) +public interface CustomerNvpmsOnConfigMapper { + + CustomerNvpmsOnConfigMapper INSTANCE = Mappers.getMapper( CustomerNvpmsOnConfigMapper.class ); + + void map(Customer customer, @MappingTarget CustomerDTO mappingTarget); + + @Mapping(source = "houseNumber", target = "houseNo") + void mapCustomerHouse(Address address, @MappingTarget AddressDTO addrDTO); + +} + diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/CustomerNvpmsOnMapperMapper.java b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/CustomerNvpmsOnMapperMapper.java new file mode 100644 index 000000000..592a687c6 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/CustomerNvpmsOnMapperMapper.java @@ -0,0 +1,24 @@ +/* + * 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.nullvaluepropertymapping; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingTarget; +import org.mapstruct.NullValuePropertyMappingStrategy; +import org.mapstruct.factory.Mappers; + +@Mapper(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE) +public interface CustomerNvpmsOnMapperMapper { + + CustomerNvpmsOnMapperMapper INSTANCE = Mappers.getMapper( CustomerNvpmsOnMapperMapper.class ); + + void map(Customer customer, @MappingTarget CustomerDTO mappingTarget); + + @Mapping(source = "houseNumber", target = "houseNo") + void mapCustomerHouse(Address address, @MappingTarget AddressDTO addrDTO); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/CustomerNvpmsPropertyMappingMapper.java b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/CustomerNvpmsPropertyMappingMapper.java new file mode 100644 index 000000000..28eaf8f33 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/CustomerNvpmsPropertyMappingMapper.java @@ -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.nullvaluepropertymapping; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingTarget; +import org.mapstruct.factory.Mappers; + +import static org.mapstruct.NullValuePropertyMappingStrategy.IGNORE; + +@Mapper +public interface CustomerNvpmsPropertyMappingMapper { + + CustomerNvpmsPropertyMappingMapper INSTANCE = Mappers.getMapper( CustomerNvpmsPropertyMappingMapper.class ); + + @Mapping( target = "address", nullValuePropertyMappingStrategy = IGNORE) + @Mapping( target = "details", nullValuePropertyMappingStrategy = IGNORE) + void map(Customer customer, @MappingTarget CustomerDTO mappingTarget); + + @Mapping(source = "houseNumber", target = "houseNo") + void mapCustomerHouse(Address address, @MappingTarget AddressDTO addrDTO); + +} + diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/ErroneousCustomerMapper1.java b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/ErroneousCustomerMapper1.java new file mode 100644 index 000000000..3c2dd5134 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/ErroneousCustomerMapper1.java @@ -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.nullvaluepropertymapping; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingTarget; +import org.mapstruct.factory.Mappers; + +import static org.mapstruct.NullValuePropertyMappingStrategy.IGNORE; + +@Mapper +public interface ErroneousCustomerMapper1 { + + ErroneousCustomerMapper1 INSTANCE = Mappers.getMapper( ErroneousCustomerMapper1.class ); + + @Mapping(target = "details", defaultValue = "test", nullValuePropertyMappingStrategy = IGNORE) + @Mapping(target = "address", nullValuePropertyMappingStrategy = IGNORE) + void map(Customer customer, @MappingTarget CustomerDTO mappingTarget); + + @Mapping(source = "houseNumber", target = "houseNo") + void mapCustomerHouse(Address address, @MappingTarget AddressDTO addrDTO); + +} + diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/ErroneousCustomerMapper2.java b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/ErroneousCustomerMapper2.java new file mode 100644 index 000000000..4b7f2bec7 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/ErroneousCustomerMapper2.java @@ -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.nullvaluepropertymapping; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingTarget; +import org.mapstruct.factory.Mappers; + +import static org.mapstruct.NullValuePropertyMappingStrategy.IGNORE; + +@Mapper +public interface ErroneousCustomerMapper2 { + + ErroneousCustomerMapper2 INSTANCE = Mappers.getMapper( ErroneousCustomerMapper2.class ); + + @Mapping(target = "details", expression = "java(getTest())", nullValuePropertyMappingStrategy = IGNORE) + @Mapping(target = "address", nullValuePropertyMappingStrategy = IGNORE) + void map(Customer customer, @MappingTarget CustomerDTO mappingTarget); + + @Mapping(source = "houseNumber", target = "houseNo") + void mapCustomerHouse(Address address, @MappingTarget AddressDTO addrDTO); + +} + diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/ErroneousCustomerMapper3.java b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/ErroneousCustomerMapper3.java new file mode 100644 index 000000000..755b46853 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/ErroneousCustomerMapper3.java @@ -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.nullvaluepropertymapping; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingTarget; +import org.mapstruct.factory.Mappers; + +import static org.mapstruct.NullValuePropertyMappingStrategy.IGNORE; + +@Mapper +public interface ErroneousCustomerMapper3 { + + ErroneousCustomerMapper3 INSTANCE = Mappers.getMapper( ErroneousCustomerMapper3.class ); + + @Mapping(target = "details", defaultExpression = "java(getTest())", nullValuePropertyMappingStrategy = IGNORE) + @Mapping(target = "address", nullValuePropertyMappingStrategy = IGNORE) + void map(Customer customer, @MappingTarget CustomerDTO mappingTarget); + + @Mapping(source = "houseNumber", target = "houseNo") + void mapCustomerHouse(Address address, @MappingTarget AddressDTO addrDTO); + +} + diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/ErroneousCustomerMapper4.java b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/ErroneousCustomerMapper4.java new file mode 100644 index 000000000..88fe1a7cb --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/ErroneousCustomerMapper4.java @@ -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.nullvaluepropertymapping; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingTarget; +import org.mapstruct.factory.Mappers; + +import static org.mapstruct.NullValuePropertyMappingStrategy.IGNORE; + +@Mapper +public interface ErroneousCustomerMapper4 { + + ErroneousCustomerMapper4 INSTANCE = Mappers.getMapper( ErroneousCustomerMapper4.class ); + + @Mapping(target = "details", constant = "test", nullValuePropertyMappingStrategy = IGNORE) + @Mapping(target = "address", nullValuePropertyMappingStrategy = IGNORE) + void map(Customer customer, @MappingTarget CustomerDTO mappingTarget); + + @Mapping(source = "houseNumber", target = "houseNo") + void mapCustomerHouse(Address address, @MappingTarget AddressDTO addrDTO); + +} + diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/ErroneousCustomerMapper5.java b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/ErroneousCustomerMapper5.java new file mode 100644 index 000000000..05ed1c1fe --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/ErroneousCustomerMapper5.java @@ -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.nullvaluepropertymapping; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingTarget; +import org.mapstruct.factory.Mappers; + +import static org.mapstruct.NullValuePropertyMappingStrategy.IGNORE; + +@Mapper +public interface ErroneousCustomerMapper5 { + + ErroneousCustomerMapper5 INSTANCE = Mappers.getMapper( ErroneousCustomerMapper5.class ); + + @Mapping(target = "details", ignore = true, nullValuePropertyMappingStrategy = IGNORE) + @Mapping(target = "address", nullValuePropertyMappingStrategy = IGNORE) + void map(Customer customer, @MappingTarget CustomerDTO mappingTarget); + + @Mapping(source = "houseNumber", target = "houseNo") + void mapCustomerHouse(Address address, @MappingTarget AddressDTO addrDTO); + +} + diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/HomeDTO.java b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/HomeDTO.java new file mode 100644 index 000000000..ad283e5ef --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/HomeDTO.java @@ -0,0 +1,19 @@ +/* + * 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.nullvaluepropertymapping; + +public class HomeDTO { + + private AddressDTO addressDTO; + + public AddressDTO getAddressDTO() { + return addressDTO; + } + + public void setAddressDTO(AddressDTO addressDTO) { + this.addressDTO = addressDTO; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/NullValuePropertyMappingTest.java b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/NullValuePropertyMappingTest.java new file mode 100644 index 000000000..63bb6f92e --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/NullValuePropertyMappingTest.java @@ -0,0 +1,203 @@ +/* + * 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.nullvaluepropertymapping; + +import java.util.Arrays; +import java.util.function.BiConsumer; + +import org.junit.Ignore; +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.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.AnnotationProcessorTestRunner; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Sjaak Derksen + */ +@IssueKey("1306") +@RunWith(AnnotationProcessorTestRunner.class) +@WithClasses({ + Address.class, + Customer.class, + CustomerDTO.class, + AddressDTO.class, + HomeDTO.class, + UserDTO.class +}) +public class NullValuePropertyMappingTest { + + @Test + @WithClasses(CustomerMapper.class) + public void testStrategyAppliedOnForgedMethod() { + + Customer customer = new Customer(); + customer.setAddress( null ); + + UserDTO userDTO = new UserDTO(); + userDTO.setHomeDTO( new HomeDTO() ); + userDTO.getHomeDTO().setAddressDTO( new AddressDTO() ); + userDTO.getHomeDTO().getAddressDTO().setHouseNo( 5 ); + userDTO.setDetails( Arrays.asList( "green hair" ) ); + + CustomerMapper.INSTANCE.mapCustomer( customer, userDTO ); + + assertThat( userDTO.getHomeDTO() ).isNotNull(); + assertThat( userDTO.getHomeDTO().getAddressDTO() ).isNotNull(); + assertThat( userDTO.getHomeDTO().getAddressDTO().getHouseNo() ).isEqualTo( 5 ); + assertThat( userDTO.getDetails() ).isNotNull(); + assertThat( userDTO.getDetails() ).containsExactly( "green hair" ); + } + + @Test + @WithClasses({ NvpmsConfig.class, CustomerNvpmsOnConfigMapper.class }) + public void testHierarchyIgnoreOnConfig() { + testConfig( ( Customer s, CustomerDTO t ) -> CustomerNvpmsOnConfigMapper.INSTANCE.map( s, t ) ); + } + + @Test + @WithClasses(CustomerNvpmsOnMapperMapper.class) + public void testHierarchyIgnoreOnMapping() { + testConfig( ( Customer s, CustomerDTO t ) -> CustomerNvpmsOnMapperMapper.INSTANCE.map( s, t ) ); + } + + @Test + @WithClasses(CustomerNvpmsOnBeanMappingMethodMapper.class) + public void testHierarchyIgnoreOnBeanMappingMethod() { + testConfig( ( Customer s, CustomerDTO t ) -> CustomerNvpmsOnBeanMappingMethodMapper.INSTANCE.map( s, t ) ); + } + + @Test + @WithClasses(CustomerNvpmsPropertyMappingMapper.class) + public void testHierarchyIgnoreOnPropertyMappingMehtod() { + testConfig( ( Customer s, CustomerDTO t ) -> CustomerNvpmsPropertyMappingMapper.INSTANCE.map( s, t ) ); + } + + @Test + @WithClasses(CustomerDefaultMapper.class) + public void testStrategyDefaultAppliedOnForgedMethod() { + + Customer customer = new Customer(); + customer.setAddress( null ); + + UserDTO userDTO = new UserDTO(); + userDTO.setHomeDTO( new HomeDTO() ); + userDTO.getHomeDTO().setAddressDTO( new AddressDTO() ); + userDTO.getHomeDTO().getAddressDTO().setHouseNo( 5 ); + userDTO.setDetails( Arrays.asList( "green hair" ) ); + + CustomerDefaultMapper.INSTANCE.mapCustomer( customer, userDTO ); + + assertThat( userDTO.getHomeDTO() ).isNotNull(); + assertThat( userDTO.getHomeDTO().getAddressDTO() ).isNotNull(); + assertThat( userDTO.getHomeDTO().getAddressDTO().getHouseNo() ).isNull(); + assertThat( userDTO.getDetails() ).isNotNull(); + assertThat( userDTO.getDetails() ).isEmpty(); + } + + @Test + @Ignore // test gives different results for JDK and JDT + @WithClasses(ErroneousCustomerMapper1.class) + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic(type = ErroneousCustomerMapper1.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 20, + messageRegExp = "Default value and nullValuePropertyMappingStrategy are both defined in @Mapping, " + + "either define a defaultValue or an nullValuePropertyMappingStrategy.") + } + ) + public void testBothDefaultValueAndNvpmsDefined() { + } + + @Test + @Ignore // test gives different results for JDK and JDT + @WithClasses(ErroneousCustomerMapper2.class) + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic(type = ErroneousCustomerMapper2.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 20, + messageRegExp = "Expression and nullValuePropertyMappingStrategy are both defined in @Mapping, " + + "either define an expression or an nullValuePropertyMappingStrategy.") + } + ) + public void testBothExpressionAndNvpmsDefined() { + } + + @Test + @Ignore // test gives different results for JDK and JDT + @WithClasses(ErroneousCustomerMapper3.class) + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic(type = ErroneousCustomerMapper3.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 20, + messageRegExp = "DefaultExpression and nullValuePropertyMappingStrategy are both defined in " + + "@Mapping, either define a defaultExpression or an nullValuePropertyMappingStrategy.") + } + ) + public void testBothDefaultExpressionAndNvpmsDefined() { + } + + @Test + @Ignore // test gives different results for JDK and JDT + @WithClasses(ErroneousCustomerMapper4.class) + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic(type = ErroneousCustomerMapper4.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 20, + messageRegExp = "Constant and nullValuePropertyMappingStrategy are both defined in @Mapping, " + + "either define a constant or an nullValuePropertyMappingStrategy.") + } + ) + public void testBothConstantAndNvpmsDefined() { + } + + @Test + @Ignore // test gives different results for JDK and JDT + @WithClasses(ErroneousCustomerMapper5.class) + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic(type = ErroneousCustomerMapper5.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 20, + messageRegExp = "Ignore and nullValuePropertyMappingStrategy are both defined in @Mapping, " + + "either define ignore or an nullValuePropertyMappingStrategy.") + } + ) + public void testBothIgnoreAndNvpmsDefined() { + } + + private void testConfig(BiConsumer customerMapper) { + + Customer customer = new Customer(); + customer.setAddress( null ); + + CustomerDTO customerDto = new CustomerDTO(); + customerDto.setAddress( new AddressDTO() ); + customerDto.getAddress().setHouseNo( 5 ); + customerDto.setDetails( Arrays.asList( "green hair" ) ); + + customerMapper.accept( customer, customerDto ); + + assertThat( customerDto.getAddress() ).isNotNull(); + assertThat( customerDto.getAddress().getHouseNo() ).isEqualTo( 5 ); + assertThat( customerDto.getDetails() ).isNotNull(); + assertThat( customerDto.getDetails() ).containsExactly( "green hair" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/NvpmsConfig.java b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/NvpmsConfig.java new file mode 100644 index 000000000..bf89b1e98 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/NvpmsConfig.java @@ -0,0 +1,13 @@ +/* + * 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.nullvaluepropertymapping; + +import org.mapstruct.MapperConfig; +import org.mapstruct.NullValuePropertyMappingStrategy; + +@MapperConfig( nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE ) +public interface NvpmsConfig { +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/UserDTO.java b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/UserDTO.java new file mode 100644 index 000000000..9dc9af6ef --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/UserDTO.java @@ -0,0 +1,30 @@ +/* + * 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.nullvaluepropertymapping; + +import java.util.List; + +public class UserDTO { + + private HomeDTO homeDTO; + private List details; + + public HomeDTO getHomeDTO() { + return homeDTO; + } + + public void setHomeDTO(HomeDTO homeDTO) { + this.homeDTO = homeDTO; + } + + public List getDetails() { + return details; + } + + public void setDetails(List details) { + this.details = details; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/selection/qualifier/QualifierTest.java b/processor/src/test/java/org/mapstruct/ap/test/selection/qualifier/QualifierTest.java index 7454b36f8..570922b6c 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/selection/qualifier/QualifierTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/selection/qualifier/QualifierTest.java @@ -174,8 +174,8 @@ public class QualifierTest { @Diagnostic(type = ErroneousMovieFactoryMapper.class, kind = Kind.ERROR, line = 24, - messageRegExp = "'nullValueMappingStrategy', 'resultType' and 'qualifiedBy' are undefined in " + - "@BeanMapping, define at least one of them."), + messageRegExp = "'nullValueMappingStrategy', 'nullValuePropertyMappingStrategy', 'resultType' and " + + "'qualifiedBy' are undefined in @BeanMapping, define at least one of them."), @Diagnostic(type = ErroneousMovieFactoryMapper.class, kind = Kind.ERROR, line = 24, diff --git a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/bugs/_913/DomainDtoWithNcvsAlwaysMapperImpl.java b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/bugs/_913/DomainDtoWithNcvsAlwaysMapperImpl.java index 3e45e345e..bd1575a20 100644 --- a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/bugs/_913/DomainDtoWithNcvsAlwaysMapperImpl.java +++ b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/bugs/_913/DomainDtoWithNcvsAlwaysMapperImpl.java @@ -26,27 +26,18 @@ public class DomainDtoWithNcvsAlwaysMapperImpl implements DomainDtoWithNcvsAlway return null; } - Domain domain = new Domain(); + Domain domain = createNullDomain(); if ( source.hasStringsInitialized() ) { domain.setLongsInitialized( stringListToLongSet( source.getStringsInitialized() ) ); } - else { - domain.setLongsInitialized( null ); - } if ( source.hasStrings() ) { domain.setLongs( stringListToLongSet( source.getStrings() ) ); } - else { - domain.setLongs( null ); - } if ( source.hasStrings() ) { List list = source.getStrings(); domain.setStrings( new HashSet( list ) ); } - else { - domain.setStrings( null ); - } if ( source.hasStringsWithDefault() ) { List list1 = source.getStringsWithDefault(); domain.setStringsWithDefault( new ArrayList( list1 ) ); @@ -58,9 +49,6 @@ public class DomainDtoWithNcvsAlwaysMapperImpl implements DomainDtoWithNcvsAlway List list2 = source.getStringsInitialized(); domain.setStringsInitialized( new HashSet( list2 ) ); } - else { - domain.setStringsInitialized( null ); - } return domain; } diff --git a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/bugs/_913/DomainDtoWithNvmsDefaultMapperImpl.java b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/bugs/_913/DomainDtoWithNvmsDefaultMapperImpl.java index 137282b10..3ed50cb86 100644 --- a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/bugs/_913/DomainDtoWithNvmsDefaultMapperImpl.java +++ b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/bugs/_913/DomainDtoWithNvmsDefaultMapperImpl.java @@ -32,9 +32,6 @@ public class DomainDtoWithNvmsDefaultMapperImpl implements DomainDtoWithNvmsDefa if ( list != null ) { domain.setStrings( new HashSet( list ) ); } - else { - domain.setStrings( new HashSet() ); - } List list1 = source.getStringsWithDefault(); if ( list1 != null ) { domain.setStringsWithDefault( new ArrayList( list1 ) ); @@ -46,9 +43,6 @@ public class DomainDtoWithNvmsDefaultMapperImpl implements DomainDtoWithNvmsDefa if ( list2 != null ) { domain.setStringsInitialized( new HashSet( list2 ) ); } - else { - domain.setStringsInitialized( new HashSet() ); - } } return domain; diff --git a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/bugs/_913/DomainDtoWithNvmsNullMapperImpl.java b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/bugs/_913/DomainDtoWithNvmsNullMapperImpl.java index b1b8b0963..34867626a 100644 --- a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/bugs/_913/DomainDtoWithNvmsNullMapperImpl.java +++ b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/bugs/_913/DomainDtoWithNvmsNullMapperImpl.java @@ -26,7 +26,7 @@ public class DomainDtoWithNvmsNullMapperImpl implements DomainDtoWithNvmsNullMap return null; } - Domain domain = new Domain(); + Domain domain = createNullDomain(); domain.setLongsInitialized( stringListToLongSet( source.getStringsInitialized() ) ); domain.setLongs( stringListToLongSet( source.getStrings() ) ); @@ -34,9 +34,6 @@ public class DomainDtoWithNvmsNullMapperImpl implements DomainDtoWithNvmsNullMap if ( list != null ) { domain.setStrings( new HashSet( list ) ); } - else { - domain.setStrings( null ); - } List list1 = source.getStringsWithDefault(); if ( list1 != null ) { domain.setStringsWithDefault( new ArrayList( list1 ) ); @@ -48,9 +45,6 @@ public class DomainDtoWithNvmsNullMapperImpl implements DomainDtoWithNvmsNullMap if ( list2 != null ) { domain.setStringsInitialized( new HashSet( list2 ) ); } - else { - domain.setStringsInitialized( null ); - } return domain; } diff --git a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/bugs/_913/DomainDtoWithPresenceCheckMapperImpl.java b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/bugs/_913/DomainDtoWithPresenceCheckMapperImpl.java index 085e89d5b..c44a9bc36 100644 --- a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/bugs/_913/DomainDtoWithPresenceCheckMapperImpl.java +++ b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/bugs/_913/DomainDtoWithPresenceCheckMapperImpl.java @@ -26,7 +26,7 @@ public class DomainDtoWithPresenceCheckMapperImpl implements DomainDtoWithPresen return null; } - Domain domain = new Domain(); + Domain domain = createNullDomain(); domain.setLongsInitialized( stringListToLongSet( source.getStringsInitialized() ) ); domain.setLongs( stringListToLongSet( source.getStrings() ) ); @@ -34,9 +34,6 @@ public class DomainDtoWithPresenceCheckMapperImpl implements DomainDtoWithPresen List list = source.getStrings(); domain.setStrings( new HashSet( list ) ); } - else { - domain.setStrings( null ); - } if ( source.hasStringsWithDefault() ) { List list1 = source.getStringsWithDefault(); domain.setStringsWithDefault( new ArrayList( list1 ) ); @@ -48,9 +45,6 @@ public class DomainDtoWithPresenceCheckMapperImpl implements DomainDtoWithPresen List list2 = source.getStringsInitialized(); domain.setStringsInitialized( new HashSet( list2 ) ); } - else { - domain.setStringsInitialized( null ); - } return domain; }