#2953 Add support for globally defining nullValueMapMappingStrategy and nullValueIterableMappingStrategy

This commit is contained in:
Filip Hrisafov 2022-11-13 14:22:25 +01:00 committed by GitHub
parent 82b19b0d8a
commit fd27380185
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 279 additions and 3 deletions

View File

@ -266,6 +266,30 @@ If a policy is given for a specific bean mapping via `@BeanMapping#ignoreUnmappe
disableBuilders` disableBuilders`
|If set to `true`, then MapStruct will not use builder patterns when doing the mapping. This is equivalent to doing `@Mapper( builder = @Builder( disableBuilder = true ) )` for all of your mappers. |If set to `true`, then MapStruct will not use builder patterns when doing the mapping. This is equivalent to doing `@Mapper( builder = @Builder( disableBuilder = true ) )` for all of your mappers.
|`false` |`false`
|`mapstruct.nullValueIterableMappingStrategy`
|The strategy to be applied when `null` is passed as a source value to an iterable mapping.
Supported values are:
* `RETURN_NULL`: if `null` is passed as a source value, then `null` will be returned
* `RETURN_DEFAULT`: if `null` is passed then a default value (empty collection) will be returned
If a strategy is given for a specific mapper via `@Mapper#nullValueIterableMappingStrategy()`, the value from the annotation takes precedence.
If a strategy is given for a specific iterable mapping via `@IterableMapping#nullValueMappingStrategy()`, it takes precedence over both `@Mapper#nullValueIterableMappingStrategy()` and the option.
|`RETURN_NULL`
|`mapstruct.nullValueMapMappingStrategy`
|The strategy to be applied when `null` is passed as a source value to a map mapping.
Supported values are:
* `RETURN_NULL`: if `null` is passed as a source value, then `null` will be returned
* `RETURN_DEFAULT`: if `null` is passed then a default value (empty map) will be returned
If a strategy is given for a specific mapper via `@Mapper#nullValueMapMappingStrategy()`, the value from the annotation takes precedence.
If a strategy is given for a specific map mapping via `@MapMapping#nullValueMappingStrategy()`, it takes precedence over both `@Mapper#nullValueMapMappingStrategy()` and the option.
|`RETURN_NULL`
|=== |===
=== Using MapStruct with the Java Module System === Using MapStruct with the Java Module System

View File

@ -12,6 +12,7 @@ import java.util.Comparator;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.ServiceLoader; import java.util.ServiceLoader;
import java.util.Set; import java.util.Set;
@ -32,6 +33,7 @@ import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.ElementKindVisitor6; import javax.lang.model.util.ElementKindVisitor6;
import javax.tools.Diagnostic.Kind; import javax.tools.Diagnostic.Kind;
import org.mapstruct.ap.internal.gem.NullValueMappingStrategyGem;
import org.mapstruct.ap.internal.model.Mapper; import org.mapstruct.ap.internal.model.Mapper;
import org.mapstruct.ap.internal.option.Options; import org.mapstruct.ap.internal.option.Options;
import org.mapstruct.ap.internal.gem.MapperGem; import org.mapstruct.ap.internal.gem.MapperGem;
@ -87,7 +89,9 @@ import static javax.lang.model.element.ElementKind.CLASS;
MappingProcessor.DEFAULT_COMPONENT_MODEL, MappingProcessor.DEFAULT_COMPONENT_MODEL,
MappingProcessor.DEFAULT_INJECTION_STRATEGY, MappingProcessor.DEFAULT_INJECTION_STRATEGY,
MappingProcessor.DISABLE_BUILDERS, MappingProcessor.DISABLE_BUILDERS,
MappingProcessor.VERBOSE MappingProcessor.VERBOSE,
MappingProcessor.NULL_VALUE_ITERABLE_MAPPING_STRATEGY,
MappingProcessor.NULL_VALUE_MAP_MAPPING_STRATEGY,
}) })
public class MappingProcessor extends AbstractProcessor { public class MappingProcessor extends AbstractProcessor {
@ -106,6 +110,8 @@ public class MappingProcessor extends AbstractProcessor {
protected static final String ALWAYS_GENERATE_SERVICE_FILE = "mapstruct.alwaysGenerateServicesFile"; protected static final String ALWAYS_GENERATE_SERVICE_FILE = "mapstruct.alwaysGenerateServicesFile";
protected static final String DISABLE_BUILDERS = "mapstruct.disableBuilders"; protected static final String DISABLE_BUILDERS = "mapstruct.disableBuilders";
protected static final String VERBOSE = "mapstruct.verbose"; protected static final String VERBOSE = "mapstruct.verbose";
protected static final String NULL_VALUE_ITERABLE_MAPPING_STRATEGY = "mapstruct.nullValueIterableMappingStrategy";
protected static final String NULL_VALUE_MAP_MAPPING_STRATEGY = "mapstruct.nullValueMapMappingStrategy";
private Options options; private Options options;
@ -139,6 +145,9 @@ public class MappingProcessor extends AbstractProcessor {
private Options createOptions() { private Options createOptions() {
String unmappedTargetPolicy = processingEnv.getOptions().get( UNMAPPED_TARGET_POLICY ); String unmappedTargetPolicy = processingEnv.getOptions().get( UNMAPPED_TARGET_POLICY );
String unmappedSourcePolicy = processingEnv.getOptions().get( UNMAPPED_SOURCE_POLICY ); String unmappedSourcePolicy = processingEnv.getOptions().get( UNMAPPED_SOURCE_POLICY );
String nullValueIterableMappingStrategy = processingEnv.getOptions()
.get( NULL_VALUE_ITERABLE_MAPPING_STRATEGY );
String nullValueMapMappingStrategy = processingEnv.getOptions().get( NULL_VALUE_MAP_MAPPING_STRATEGY );
return new Options( return new Options(
Boolean.parseBoolean( processingEnv.getOptions().get( SUPPRESS_GENERATOR_TIMESTAMP ) ), Boolean.parseBoolean( processingEnv.getOptions().get( SUPPRESS_GENERATOR_TIMESTAMP ) ),
@ -149,7 +158,12 @@ public class MappingProcessor extends AbstractProcessor {
processingEnv.getOptions().get( DEFAULT_INJECTION_STRATEGY ), processingEnv.getOptions().get( DEFAULT_INJECTION_STRATEGY ),
Boolean.parseBoolean( processingEnv.getOptions().get( ALWAYS_GENERATE_SERVICE_FILE ) ), Boolean.parseBoolean( processingEnv.getOptions().get( ALWAYS_GENERATE_SERVICE_FILE ) ),
Boolean.parseBoolean( processingEnv.getOptions().get( DISABLE_BUILDERS ) ), Boolean.parseBoolean( processingEnv.getOptions().get( DISABLE_BUILDERS ) ),
Boolean.parseBoolean( processingEnv.getOptions().get( VERBOSE ) ) Boolean.parseBoolean( processingEnv.getOptions().get( VERBOSE ) ),
nullValueIterableMappingStrategy != null ?
NullValueMappingStrategyGem.valueOf( nullValueIterableMappingStrategy.toUpperCase( Locale.ROOT ) ) :
null,
nullValueMapMappingStrategy != null ?
NullValueMappingStrategyGem.valueOf( nullValueMapMappingStrategy.toUpperCase( Locale.ROOT ) ) : null
); );
} }

View File

@ -132,10 +132,18 @@ public class DefaultOptions extends DelegatingOptions {
} }
public NullValueMappingStrategyGem getNullValueIterableMappingStrategy() { public NullValueMappingStrategyGem getNullValueIterableMappingStrategy() {
NullValueMappingStrategyGem nullValueIterableMappingStrategy = options.getNullValueIterableMappingStrategy();
if ( nullValueIterableMappingStrategy != null ) {
return nullValueIterableMappingStrategy;
}
return NullValueMappingStrategyGem.valueOf( mapper.nullValueIterableMappingStrategy().getDefaultValue() ); return NullValueMappingStrategyGem.valueOf( mapper.nullValueIterableMappingStrategy().getDefaultValue() );
} }
public NullValueMappingStrategyGem getNullValueMapMappingStrategy() { public NullValueMappingStrategyGem getNullValueMapMappingStrategy() {
NullValueMappingStrategyGem nullValueMapMappingStrategy = options.getNullValueMapMappingStrategy();
if ( nullValueMapMappingStrategy != null ) {
return nullValueMapMappingStrategy;
}
return NullValueMappingStrategyGem.valueOf( mapper.nullValueMapMappingStrategy().getDefaultValue() ); return NullValueMappingStrategyGem.valueOf( mapper.nullValueMapMappingStrategy().getDefaultValue() );
} }

View File

@ -5,6 +5,7 @@
*/ */
package org.mapstruct.ap.internal.option; package org.mapstruct.ap.internal.option;
import org.mapstruct.ap.internal.gem.NullValueMappingStrategyGem;
import org.mapstruct.ap.internal.gem.ReportingPolicyGem; import org.mapstruct.ap.internal.gem.ReportingPolicyGem;
/** /**
@ -23,14 +24,21 @@ public class Options {
private final String defaultInjectionStrategy; private final String defaultInjectionStrategy;
private final boolean disableBuilders; private final boolean disableBuilders;
private final boolean verbose; private final boolean verbose;
private final NullValueMappingStrategyGem nullValueIterableMappingStrategy;
private final NullValueMappingStrategyGem nullValueMapMappingStrategy;
//CHECKSTYLE:OFF
public Options(boolean suppressGeneratorTimestamp, boolean suppressGeneratorVersionComment, public Options(boolean suppressGeneratorTimestamp, boolean suppressGeneratorVersionComment,
ReportingPolicyGem unmappedTargetPolicy, ReportingPolicyGem unmappedTargetPolicy,
ReportingPolicyGem unmappedSourcePolicy, ReportingPolicyGem unmappedSourcePolicy,
String defaultComponentModel, String defaultInjectionStrategy, String defaultComponentModel, String defaultInjectionStrategy,
boolean alwaysGenerateSpi, boolean alwaysGenerateSpi,
boolean disableBuilders, boolean disableBuilders,
boolean verbose) { boolean verbose,
NullValueMappingStrategyGem nullValueIterableMappingStrategy,
NullValueMappingStrategyGem nullValueMapMappingStrategy
) {
//CHECKSTYLE:ON
this.suppressGeneratorTimestamp = suppressGeneratorTimestamp; this.suppressGeneratorTimestamp = suppressGeneratorTimestamp;
this.suppressGeneratorVersionComment = suppressGeneratorVersionComment; this.suppressGeneratorVersionComment = suppressGeneratorVersionComment;
this.unmappedTargetPolicy = unmappedTargetPolicy; this.unmappedTargetPolicy = unmappedTargetPolicy;
@ -40,6 +48,8 @@ public class Options {
this.alwaysGenerateSpi = alwaysGenerateSpi; this.alwaysGenerateSpi = alwaysGenerateSpi;
this.disableBuilders = disableBuilders; this.disableBuilders = disableBuilders;
this.verbose = verbose; this.verbose = verbose;
this.nullValueIterableMappingStrategy = nullValueIterableMappingStrategy;
this.nullValueMapMappingStrategy = nullValueMapMappingStrategy;
} }
public boolean isSuppressGeneratorTimestamp() { public boolean isSuppressGeneratorTimestamp() {
@ -77,4 +87,12 @@ public class Options {
public boolean isVerbose() { public boolean isVerbose() {
return verbose; return verbose;
} }
public NullValueMappingStrategyGem getNullValueIterableMappingStrategy() {
return nullValueIterableMappingStrategy;
}
public NullValueMappingStrategyGem getNullValueMapMappingStrategy() {
return nullValueMapMappingStrategy;
}
} }

View File

@ -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.nullvaluemapping;
import java.util.List;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.ap.test.nullvaluemapping._target.CarDto;
import org.mapstruct.ap.test.nullvaluemapping.source.Car;
import org.mapstruct.factory.Mappers;
/**
* @author Filip Hrisafov
*/
@Mapper
public interface CarListMapper {
CarListMapper INSTANCE = Mappers.getMapper( CarListMapper.class );
@Mapping(target = "seatCount", ignore = true)
@Mapping(target = "model", ignore = true)
@Mapping(target = "catalogId", ignore = true)
CarDto map(Car car);
List<CarDto> carsToCarDtoList(List<Car> cars);
}

View File

@ -0,0 +1,31 @@
/*
* 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.nullvaluemapping;
import java.util.List;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.NullValueMappingStrategy;
import org.mapstruct.ap.test.nullvaluemapping._target.CarDto;
import org.mapstruct.ap.test.nullvaluemapping.source.Car;
import org.mapstruct.factory.Mappers;
/**
* @author Filip Hrisafov
*/
@Mapper(nullValueIterableMappingStrategy = NullValueMappingStrategy.RETURN_NULL)
public interface CarListMapperSettingOnMapper {
CarListMapperSettingOnMapper INSTANCE = Mappers.getMapper( CarListMapperSettingOnMapper.class );
@Mapping(target = "seatCount", ignore = true)
@Mapping(target = "model", ignore = true)
@Mapping(target = "catalogId", ignore = true)
CarDto map(Car car);
List<CarDto> carsToCarDtoList(List<Car> cars);
}

View File

@ -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.nullvaluemapping;
import java.util.Map;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.ap.test.nullvaluemapping._target.CarDto;
import org.mapstruct.ap.test.nullvaluemapping.source.Car;
import org.mapstruct.factory.Mappers;
/**
* @author Filip Hrisafov
*/
@Mapper
public interface CarMapMapper {
CarMapMapper INSTANCE = Mappers.getMapper( CarMapMapper.class );
@Mapping(target = "seatCount", ignore = true)
@Mapping(target = "model", ignore = true)
@Mapping(target = "catalogId", ignore = true)
CarDto map(Car car);
Map<Integer, CarDto> carsToCarDtoMap(Map<Integer, Car> cars);
}

View File

@ -0,0 +1,31 @@
/*
* 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.nullvaluemapping;
import java.util.Map;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.NullValueMappingStrategy;
import org.mapstruct.ap.test.nullvaluemapping._target.CarDto;
import org.mapstruct.ap.test.nullvaluemapping.source.Car;
import org.mapstruct.factory.Mappers;
/**
* @author Filip Hrisafov
*/
@Mapper(nullValueMapMappingStrategy = NullValueMappingStrategy.RETURN_NULL)
public interface CarMapMapperSettingOnMapper {
CarMapMapperSettingOnMapper INSTANCE = Mappers.getMapper( CarMapMapperSettingOnMapper.class );
@Mapping(target = "seatCount", ignore = true)
@Mapping(target = "model", ignore = true)
@Mapping(target = "catalogId", ignore = true)
CarDto map(Car car);
Map<Integer, CarDto> carsToCarDtoMap(Map<Integer, Car> cars);
}

View File

@ -0,0 +1,45 @@
/*
* 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.nullvaluemapping;
import org.mapstruct.ap.test.nullvaluemapping._target.CarDto;
import org.mapstruct.ap.test.nullvaluemapping.source.Car;
import org.mapstruct.ap.testutil.IssueKey;
import org.mapstruct.ap.testutil.ProcessorTest;
import org.mapstruct.ap.testutil.WithClasses;
import org.mapstruct.ap.testutil.compilation.annotation.ProcessorOption;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Filip Hrisafov
*/
@WithClasses({
CarDto.class,
Car.class
})
@IssueKey("2953")
public class NullValueIterableMappingStrategyTest {
@ProcessorTest
@ProcessorOption(name = "mapstruct.nullValueIterableMappingStrategy", value = "return_default")
@WithClasses({
CarListMapper.class
})
void globalNullIterableMappingStrategy() {
assertThat( CarListMapper.INSTANCE.carsToCarDtoList( null ) ).isEmpty();
}
@ProcessorTest
@ProcessorOption(name = "mapstruct.nullValueIterableMappingStrategy", value = "return_default")
@WithClasses({
CarListMapperSettingOnMapper.class
})
void globalNullMapMappingStrategyWithOverrideInMapper() {
// Explicit definition in @Mapper should override global
assertThat( CarListMapperSettingOnMapper.INSTANCE.carsToCarDtoList( null ) ).isNull();
}
}

View File

@ -0,0 +1,45 @@
/*
* 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.nullvaluemapping;
import org.mapstruct.ap.test.nullvaluemapping._target.CarDto;
import org.mapstruct.ap.test.nullvaluemapping.source.Car;
import org.mapstruct.ap.testutil.IssueKey;
import org.mapstruct.ap.testutil.ProcessorTest;
import org.mapstruct.ap.testutil.WithClasses;
import org.mapstruct.ap.testutil.compilation.annotation.ProcessorOption;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Filip Hrisafov
*/
@WithClasses({
CarDto.class,
Car.class
})
@IssueKey("2953")
public class NullValueMapMappingStrategyTest {
@ProcessorTest
@ProcessorOption(name = "mapstruct.nullValueMapMappingStrategy", value = "return_default")
@WithClasses({
CarMapMapper.class
})
void globalNullMapMappingStrategy() {
assertThat( CarMapMapper.INSTANCE.carsToCarDtoMap( null ) ).isEmpty();
}
@ProcessorTest
@ProcessorOption(name = "mapstruct.nullValueMapMappingStrategy", value = "return_default")
@WithClasses({
CarMapMapperSettingOnMapper.class
})
void globalNullMapMappingStrategyWithOverrideInMapper() {
// Explicit definition in @Mapper should override global
assertThat( CarMapMapperSettingOnMapper.INSTANCE.carsToCarDtoMap( null ) ).isNull();
}
}