Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions documentation/src/main/asciidoc/chapter-2-set-up.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
18 changes: 16 additions & 2 deletions processor/src/main/java/org/mapstruct/ap/MappingProcessor.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
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;
Expand All @@ -32,6 +33,7 @@
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;
Expand Down Expand Up @@ -87,7 +89,9 @@
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 {

Expand All @@ -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;

Expand Down Expand Up @@ -139,6 +145,9 @@ public synchronized void init(ProcessingEnvironment processingEnv) {
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 ) ),
Expand All @@ -149,7 +158,12 @@ private Options createOptions() {
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
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,10 +132,18 @@ public SubclassExhaustiveStrategyGem getSubclassExhaustiveStrategy() {
}

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() );
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
*/
package org.mapstruct.ap.internal.option;

import org.mapstruct.ap.internal.gem.NullValueMappingStrategyGem;
import org.mapstruct.ap.internal.gem.ReportingPolicyGem;

/**
Expand All @@ -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;
Expand All @@ -40,6 +48,8 @@ public Options(boolean suppressGeneratorTimestamp, boolean suppressGeneratorVers
this.alwaysGenerateSpi = alwaysGenerateSpi;
this.disableBuilders = disableBuilders;
this.verbose = verbose;
this.nullValueIterableMappingStrategy = nullValueIterableMappingStrategy;
this.nullValueMapMappingStrategy = nullValueMapMappingStrategy;
}

public boolean isSuppressGeneratorTimestamp() {
Expand Down Expand Up @@ -77,4 +87,12 @@ public boolean isDisableBuilders() {
public boolean isVerbose() {
return verbose;
}

public NullValueMappingStrategyGem getNullValueIterableMappingStrategy() {
return nullValueIterableMappingStrategy;
}

public NullValueMappingStrategyGem getNullValueMapMappingStrategy() {
return nullValueMapMappingStrategy;
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
Original file line number Diff line number Diff line change
@@ -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);
}
Original file line number Diff line number Diff line change
@@ -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);
}
Original file line number Diff line number Diff line change
@@ -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);
}
Original file line number Diff line number Diff line change
@@ -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();
}
}
Original file line number Diff line number Diff line change
@@ -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();
}
}