#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`
|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`
|`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

View File

@ -12,6 +12,7 @@ import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.ServiceLoader;
import java.util.Set;
@ -32,6 +33,7 @@ import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.ElementKindVisitor6;
import javax.tools.Diagnostic.Kind;
import org.mapstruct.ap.internal.gem.NullValueMappingStrategyGem;
import org.mapstruct.ap.internal.model.Mapper;
import org.mapstruct.ap.internal.option.Options;
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_INJECTION_STRATEGY,
MappingProcessor.DISABLE_BUILDERS,
MappingProcessor.VERBOSE
MappingProcessor.VERBOSE,
MappingProcessor.NULL_VALUE_ITERABLE_MAPPING_STRATEGY,
MappingProcessor.NULL_VALUE_MAP_MAPPING_STRATEGY,
})
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 DISABLE_BUILDERS = "mapstruct.disableBuilders";
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;
@ -139,6 +145,9 @@ public class MappingProcessor extends AbstractProcessor {
private Options createOptions() {
String unmappedTargetPolicy = processingEnv.getOptions().get( UNMAPPED_TARGET_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(
Boolean.parseBoolean( processingEnv.getOptions().get( SUPPRESS_GENERATOR_TIMESTAMP ) ),
@ -149,7 +158,12 @@ public class MappingProcessor extends AbstractProcessor {
processingEnv.getOptions().get( DEFAULT_INJECTION_STRATEGY ),
Boolean.parseBoolean( processingEnv.getOptions().get( ALWAYS_GENERATE_SERVICE_FILE ) ),
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() {
NullValueMappingStrategyGem nullValueIterableMappingStrategy = options.getNullValueIterableMappingStrategy();
if ( nullValueIterableMappingStrategy != null ) {
return nullValueIterableMappingStrategy;
}
return NullValueMappingStrategyGem.valueOf( mapper.nullValueIterableMappingStrategy().getDefaultValue() );
}
public NullValueMappingStrategyGem getNullValueMapMappingStrategy() {
NullValueMappingStrategyGem nullValueMapMappingStrategy = options.getNullValueMapMappingStrategy();
if ( nullValueMapMappingStrategy != null ) {
return nullValueMapMappingStrategy;
}
return NullValueMappingStrategyGem.valueOf( mapper.nullValueMapMappingStrategy().getDefaultValue() );
}

View File

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