Skip to content

Commit

Permalink
mapstruct#1792 Annotation processor option for default injection stra…
Browse files Browse the repository at this point in the history
…tegy
  • Loading branch information
Captain1653 authored and filiphr committed Sep 18, 2019
1 parent d018aed commit 750ce48
Show file tree
Hide file tree
Showing 16 changed files with 518 additions and 7 deletions.
13 changes: 13 additions & 0 deletions documentation/src/main/asciidoc/chapter-2-set-up.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,19 @@ Supported values are:
If a component model is given for a specific mapper via `@Mapper#componentModel()`, the value from the annotation takes precedence.
|`default`

|`mapstruct.defaultInjectionStrategy`
| The type of the injection in mapper via parameter `uses`. This is only used on annotated based component models
such as CDI, Spring and JSR 330.

Supported values are:

* `field`: dependencies will be injected in fields
* `constructor`: will be generated constructor. Dependencies will be injected via constructor.

When CDI `componentModel` a default constructor will also be generated.
If a injection strategy is given for a specific mapper via `@Mapper#injectionStrategy()`, the value from the annotation takes precedence over the option.
|`field`

|`mapstruct.unmappedTargetPolicy`
|The default reporting policy to be applied in case an attribute of the target object of a mapping method is not populated with a source value.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ public interface CarMapper {
The generated mapper will inject all classes defined in the **uses** attribute.
When `InjectionStrategy#CONSTRUCTOR` is used, the constructor will have the appropriate annotation and the fields won't.
When `InjectionStrategy#FIELD` is used, the annotation is on the field itself.
For now, the default injection strategy is field injection.
For now, the default injection strategy is field injection, but it can be configured with <<configuration-options>>.
It is recommended to use constructor injection to simplify testing.

[TIP]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@
MappingProcessor.SUPPRESS_GENERATOR_VERSION_INFO_COMMENT,
MappingProcessor.UNMAPPED_TARGET_POLICY,
MappingProcessor.DEFAULT_COMPONENT_MODEL,
MappingProcessor.DEFAULT_INJECTION_STRATEGY,
MappingProcessor.VERBOSE
})
public class MappingProcessor extends AbstractProcessor {
Expand All @@ -97,6 +98,7 @@ public class MappingProcessor extends AbstractProcessor {
"mapstruct.suppressGeneratorVersionInfoComment";
protected static final String UNMAPPED_TARGET_POLICY = "mapstruct.unmappedTargetPolicy";
protected static final String DEFAULT_COMPONENT_MODEL = "mapstruct.defaultComponentModel";
protected static final String DEFAULT_INJECTION_STRATEGY = "mapstruct.defaultInjectionStrategy";
protected static final String ALWAYS_GENERATE_SERVICE_FILE = "mapstruct.alwaysGenerateServicesFile";
protected static final String VERBOSE = "mapstruct.verbose";

Expand Down Expand Up @@ -136,6 +138,7 @@ private Options createOptions() {
Boolean.valueOf( processingEnv.getOptions().get( SUPPRESS_GENERATOR_VERSION_INFO_COMMENT ) ),
unmappedTargetPolicy != null ? ReportingPolicyPrism.valueOf( unmappedTargetPolicy.toUpperCase() ) : null,
processingEnv.getOptions().get( DEFAULT_COMPONENT_MODEL ),
processingEnv.getOptions().get( DEFAULT_INJECTION_STRATEGY ),
Boolean.valueOf( processingEnv.getOptions().get( ALWAYS_GENERATE_SERVICE_FILE ) ),
Boolean.valueOf( processingEnv.getOptions().get( VERBOSE ) )
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,18 @@ public class Options {
private final ReportingPolicyPrism unmappedTargetPolicy;
private final boolean alwaysGenerateSpi;
private final String defaultComponentModel;
private final String defaultInjectionStrategy;
private final boolean verbose;

public Options(boolean suppressGeneratorTimestamp, boolean suppressGeneratorVersionComment,
ReportingPolicyPrism unmappedTargetPolicy,
String defaultComponentModel, boolean alwaysGenerateSpi, boolean verbose) {
String defaultComponentModel, String defaultInjectionStrategy,
boolean alwaysGenerateSpi, boolean verbose) {
this.suppressGeneratorTimestamp = suppressGeneratorTimestamp;
this.suppressGeneratorVersionComment = suppressGeneratorVersionComment;
this.unmappedTargetPolicy = unmappedTargetPolicy;
this.defaultComponentModel = defaultComponentModel;
this.defaultInjectionStrategy = defaultInjectionStrategy;
this.alwaysGenerateSpi = alwaysGenerateSpi;
this.verbose = verbose;
}
Expand All @@ -48,6 +51,10 @@ public String getDefaultComponentModel() {
return defaultComponentModel;
}

public String getDefaultInjectionStrategy() {
return defaultInjectionStrategy;
}

public boolean isAlwaysGenerateSpi() {
return alwaysGenerateSpi;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public Mapper process(ProcessorContext context, TypeElement mapperTypeElement, M
MapperConfiguration mapperConfiguration = MapperConfiguration.getInstanceOn( mapperTypeElement );

String componentModel = mapperConfiguration.componentModel( context.getOptions() );
InjectionStrategyPrism injectionStrategy = mapperConfiguration.getInjectionStrategy();
InjectionStrategyPrism injectionStrategy = mapperConfiguration.getInjectionStrategy( context.getOptions() );

if ( !getComponentModelIdentifier().equalsIgnoreCase( componentModel ) ) {
return mapper;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -202,13 +202,21 @@ else if ( mapperConfigPrism != null && mapperPrism.values.nullValuePropertyMappi
}
}

public InjectionStrategyPrism getInjectionStrategy() {
if ( mapperConfigPrism != null && mapperPrism.values.injectionStrategy() == null ) {
public InjectionStrategyPrism getInjectionStrategy(Options options) {
if ( mapperPrism.values.injectionStrategy() != null ) {
return InjectionStrategyPrism.valueOf( mapperPrism.injectionStrategy() );
}

if ( mapperConfigPrism != null && mapperConfigPrism.values.injectionStrategy() != null ) {
return InjectionStrategyPrism.valueOf( mapperConfigPrism.injectionStrategy() );
}
else {
return InjectionStrategyPrism.valueOf( mapperPrism.injectionStrategy() );

if ( options.getDefaultInjectionStrategy() != null ) {
return InjectionStrategyPrism.valueOf( options.getDefaultInjectionStrategy().toUpperCase() );
}

// fall back to default defined in the annotation
return InjectionStrategyPrism.valueOf( mapperPrism.injectionStrategy() );
}

public NullValueMappingStrategyPrism getNullValueMappingStrategy() {
Expand Down
Original file line number Diff line number Diff line change
@@ -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.injectionstrategy.jsr330._default;

import org.mapstruct.Mapper;
import org.mapstruct.ap.test.injectionstrategy.shared.CustomerDto;
import org.mapstruct.ap.test.injectionstrategy.shared.CustomerEntity;

/**
* @author Andrei Arlou
*/
@Mapper(componentModel = "jsr330", uses = GenderJsr330DefaultCompileOptionFieldMapper.class)
public interface CustomerJsr330DefaultCompileOptionFieldMapper {

CustomerDto asTarget(CustomerEntity customerEntity);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* 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.injectionstrategy.jsr330._default;

import org.mapstruct.Mapper;
import org.mapstruct.ValueMapping;
import org.mapstruct.ValueMappings;
import org.mapstruct.ap.test.injectionstrategy.shared.Gender;
import org.mapstruct.ap.test.injectionstrategy.shared.GenderDto;

/**
* @author Andrei Arlou
*/
@Mapper(componentModel = "jsr330")
public interface GenderJsr330DefaultCompileOptionFieldMapper {

@ValueMappings({
@ValueMapping(source = "MALE", target = "M"),
@ValueMapping(source = "FEMALE", target = "F")
})
GenderDto mapToDto(Gender gender);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/*
* 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.injectionstrategy.jsr330._default;

import javax.inject.Inject;
import javax.inject.Named;

import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mapstruct.ap.test.injectionstrategy.shared.CustomerDto;
import org.mapstruct.ap.test.injectionstrategy.shared.CustomerEntity;
import org.mapstruct.ap.test.injectionstrategy.shared.Gender;
import org.mapstruct.ap.test.injectionstrategy.shared.GenderDto;
import org.mapstruct.ap.testutil.WithClasses;
import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner;
import org.mapstruct.ap.testutil.runner.GeneratedSource;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

import static java.lang.System.lineSeparator;
import static org.assertj.core.api.Assertions.assertThat;

/**
* Test field injection for component model jsr330.
* Default value option mapstruct.defaultInjectionStrategy is "field"
*
* @author Andrei Arlou
*/
@WithClasses({
CustomerDto.class,
CustomerEntity.class,
Gender.class,
GenderDto.class,
CustomerJsr330DefaultCompileOptionFieldMapper.class,
GenderJsr330DefaultCompileOptionFieldMapper.class
})
@RunWith(AnnotationProcessorTestRunner.class)
@ComponentScan(basePackageClasses = CustomerJsr330DefaultCompileOptionFieldMapper.class)
@Configuration
public class Jsr330DefaultCompileOptionFieldMapperTest {

@Rule
public final GeneratedSource generatedSource = new GeneratedSource();

@Inject
@Named
private CustomerJsr330DefaultCompileOptionFieldMapper customerMapper;
private ConfigurableApplicationContext context;

@Before
public void springUp() {
context = new AnnotationConfigApplicationContext( getClass() );
context.getAutowireCapableBeanFactory().autowireBean( this );
}

@After
public void springDown() {
if ( context != null ) {
context.close();
}
}

@Test
public void shouldConvertToTarget() {
// given
CustomerEntity customerEntity = new CustomerEntity();
customerEntity.setName( "Samuel" );
customerEntity.setGender( Gender.MALE );

// when
CustomerDto customerDto = customerMapper.asTarget( customerEntity );

// then
assertThat( customerDto ).isNotNull();
assertThat( customerDto.getName() ).isEqualTo( "Samuel" );
assertThat( customerDto.getGender() ).isEqualTo( GenderDto.M );
}

@Test
public void shouldHaveFieldInjection() {
generatedSource.forMapper( CustomerJsr330DefaultCompileOptionFieldMapper.class )
.content()
.contains( "@Inject" + lineSeparator() + " private GenderJsr330DefaultCompileOptionFieldMapper" )
.doesNotContain( "public CustomerJsr330DefaultCompileOptionFieldMapperImpl(" );
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* 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.injectionstrategy.jsr330.compileoptionconstructor;

import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.ap.test.injectionstrategy.shared.CustomerDto;
import org.mapstruct.ap.test.injectionstrategy.shared.CustomerEntity;

/**
* @author Andrei Arlou
*/
@Mapper( componentModel = "jsr330",
uses = GenderJsr330CompileOptionConstructorMapper.class )
public interface CustomerJsr330CompileOptionConstructorMapper {

@Mapping(source = "gender", target = "gender")
CustomerDto asTarget(CustomerEntity customerEntity);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* 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.injectionstrategy.jsr330.compileoptionconstructor;

import org.mapstruct.Mapper;
import org.mapstruct.ValueMapping;
import org.mapstruct.ValueMappings;
import org.mapstruct.ap.test.injectionstrategy.shared.Gender;
import org.mapstruct.ap.test.injectionstrategy.shared.GenderDto;

/**
* @author Andrei Arlou
*/
@Mapper(componentModel = "jsr330")
public interface GenderJsr330CompileOptionConstructorMapper {

@ValueMappings({
@ValueMapping(source = "MALE", target = "M"),
@ValueMapping(source = "FEMALE", target = "F")
})
GenderDto mapToDto(Gender gender);
}
Loading

0 comments on commit 750ce48

Please sign in to comment.