From 0981959ff053186c24cbb88f106a670c9ad21f9c Mon Sep 17 00:00:00 2001 From: Sjaak Derksen Date: Mon, 28 Jan 2019 22:47:37 +0100 Subject: [PATCH] #1699 add sensible defaults to NullValuePropertyMapping.SET_TO_DEFAULT (#1702) --- .../NullValuePropertyMappingStrategy.java | 11 +++++++++++ .../asciidoc/mapstruct-reference-guide.asciidoc | 10 +++++++--- .../mapstruct/ap/internal/model/common/Type.java | 16 ++++++++++++++++ .../ap/internal/model/macro/CommonMacros.ftl | 2 ++ .../mapstruct/ap/test/bugs/_1685/UserMapper.java | 1 - .../ap/test/bugs/_1685/UserMapperImpl.java | 10 +++++----- 6 files changed, 41 insertions(+), 9 deletions(-) diff --git a/core/src/main/java/org/mapstruct/NullValuePropertyMappingStrategy.java b/core/src/main/java/org/mapstruct/NullValuePropertyMappingStrategy.java index 6db84814c..9e06e723b 100644 --- a/core/src/main/java/org/mapstruct/NullValuePropertyMappingStrategy.java +++ b/core/src/main/java/org/mapstruct/NullValuePropertyMappingStrategy.java @@ -33,6 +33,17 @@ public enum NullValuePropertyMappingStrategy { /** * If a source bean property equals {@code null} the target bean property will be set to its default value. + *

+ * This means: + *

    + *
  1. For {@code List} MapStruct generates an {@code ArrayList}
  2. + *
  3. For {@code Map} a {@code HashMap}
  4. + *
  5. For arrays an empty array
  6. + *
  7. For {@code String} {@code ""}
  8. + *
  9. for primitive / boxed types a representation of {@code 0} or {@code false}
  10. + *
  11. For all other objects an new instance is created, requiring an empty constructor.
  12. + *
+ *

* Make sure that a {@link Mapping#defaultValue()} is defined if no empty constructor is available on * the default value. */ diff --git a/documentation/src/main/asciidoc/mapstruct-reference-guide.asciidoc b/documentation/src/main/asciidoc/mapstruct-reference-guide.asciidoc index 3c1ea5649..46a996353 100644 --- a/documentation/src/main/asciidoc/mapstruct-reference-guide.asciidoc +++ b/documentation/src/main/asciidoc/mapstruct-reference-guide.asciidoc @@ -2134,11 +2134,15 @@ The strategy works in a hierarchical fashion. Setting `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. +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 results in 'absent'. -By default the source property will be set to null. However: +By default the target property will be set to null. -1. By specifying `nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.SET_TO_DEFAULT` on `@Mapping`, `@BeanMapping`, `@Mapper` or `@MappingConfig`, the mapping result can be altered to return *default* values. Please note that a default constructor is required. If not available, use the `@Mapping#defaultValue`. For `List` MapStruct generates an `ArrayList`, for `Map` mapstruct generates a `HashMap`. +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. +For `List` MapStruct generates an `ArrayList`, for `Map` a `HashMap`, for arrays an empty array, for `String` `""` and for primitive / boxed types a representation of `false` or `0`. +For all other objects an new instance is created. Please note that a default constructor is required. If not available, use the `@Mapping#defaultValue`. 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. diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java index 8097b15e6..5992a687b 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java @@ -934,6 +934,22 @@ public class Type extends ModelElement implements Comparable { throw new UnsupportedOperationException( getName() ); } + public String getSensibleDefault() { + if ( isPrimitive() ) { + return getNull(); + } + else if ( "String".equals( getName() ) ) { + return "\"\""; + } + else { + if ( isNative() ) { + // must be boxed, since primitive is already checked + return typeFactory.getType( typeUtils.unboxedType( typeMirror ) ).getNull(); + } + } + return null; + } + @Override public int hashCode() { // javadoc typemirror: "Types should be compared using the utility methods in Types. There is no guarantee diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/macro/CommonMacros.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/macro/CommonMacros.ftl index 8fe5c44fa..96ecd953b 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/macro/CommonMacros.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/macro/CommonMacros.ftl @@ -161,6 +161,8 @@ Performs a default assignment with a default value. new <@includeModel object=ext.targetType.implementationType/>() <#elseif ext.targetType.arrayType> new <@includeModel object=ext.targetType.componentType/>[0] + <#elseif ext.targetType.sensibleDefault??> + ${ext.targetType.sensibleDefault} <#else> new <@includeModel object=ext.targetType/>() diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1685/UserMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1685/UserMapper.java index 10ed334c0..38800dc75 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1685/UserMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1685/UserMapper.java @@ -38,7 +38,6 @@ public interface UserMapper { @InheritInverseConfiguration @BeanMapping( nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.SET_TO_DEFAULT ) - @Mapping( target = "phone", source = "contactDataDTO.phone", defaultValue = "0" ) void updateUserFromUserAndDefaultDTO(UserDTO userDTO, @MappingTarget User user); } diff --git a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/bugs/_1685/UserMapperImpl.java b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/bugs/_1685/UserMapperImpl.java index 9c62462f5..811fca4a5 100644 --- a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/bugs/_1685/UserMapperImpl.java +++ b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/bugs/_1685/UserMapperImpl.java @@ -12,8 +12,8 @@ import javax.annotation.Generated; @Generated( value = "org.mapstruct.ap.MappingProcessor", - date = "2019-01-27T12:40:32+0100", - comments = "version: , compiler: Eclipse JDT (Batch) 1.2.100.v20160418-1457, environment: Java 1.8.0_181 (Oracle Corporation)" + date = "2019-01-28T21:17:39+0100", + comments = "version: , compiler: javac, environment: Java 1.8.0_181 (Oracle Corporation)" ) public class UserMapperImpl implements UserMapper { @@ -116,7 +116,7 @@ public class UserMapperImpl implements UserMapper { user.setAddress( address ); } else { - user.setAddress( new String() ); + user.setAddress( "" ); } String phone = userDTOContactDataDTOPhone( userDTO ); if ( phone != null ) { @@ -130,13 +130,13 @@ public class UserMapperImpl implements UserMapper { user.setEmail( email ); } else { - user.setEmail( new String() ); + user.setEmail( "" ); } if ( userDTO.getName() != null ) { user.setName( userDTO.getName() ); } else { - user.setName( new String() ); + user.setName( "" ); } }