From 750ce48023e9cd0022b34e86d72f1cd5ee0d4bb7 Mon Sep 17 00:00:00 2001 From: Andrei Arlou Date: Wed, 18 Sep 2019 20:51:36 +0300 Subject: [PATCH] #1792 Annotation processor option for default injection strategy --- .../main/asciidoc/chapter-2-set-up.asciidoc | 13 ++ .../chapter-4-retrieving-a-mapper.asciidoc | 2 +- .../org/mapstruct/ap/MappingProcessor.java | 3 + .../mapstruct/ap/internal/option/Options.java | 9 +- ...nnotationBasedComponentModelProcessor.java | 2 +- .../ap/internal/util/MapperConfiguration.java | 18 ++- ...Jsr330DefaultCompileOptionFieldMapper.java | 19 +++ ...Jsr330DefaultCompileOptionFieldMapper.java | 25 ++++ ...30DefaultCompileOptionFieldMapperTest.java | 94 +++++++++++++ ...rJsr330CompileOptionConstructorMapper.java | 23 +++ ...rJsr330CompileOptionConstructorMapper.java | 25 ++++ ...330CompileOptionConstructorMapperTest.java | 95 +++++++++++++ ...dSpringCompileOptionConstructorMapper.java | 21 +++ ...rSpringCompileOptionConstructorMapper.java | 22 +++ ...rSpringCompileOptionConstructorMapper.java | 25 ++++ ...ingCompileOptionConstructorMapperTest.java | 131 ++++++++++++++++++ 16 files changed, 519 insertions(+), 8 deletions(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/_default/CustomerJsr330DefaultCompileOptionFieldMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/_default/GenderJsr330DefaultCompileOptionFieldMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/_default/Jsr330DefaultCompileOptionFieldMapperTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/compileoptionconstructor/CustomerJsr330CompileOptionConstructorMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/compileoptionconstructor/GenderJsr330CompileOptionConstructorMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/compileoptionconstructor/Jsr330CompileOptionConstructorMapperTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/compileoptionconstructor/CustomerRecordSpringCompileOptionConstructorMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/compileoptionconstructor/CustomerSpringCompileOptionConstructorMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/compileoptionconstructor/GenderSpringCompileOptionConstructorMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/compileoptionconstructor/SpringCompileOptionConstructorMapperTest.java diff --git a/documentation/src/main/asciidoc/chapter-2-set-up.asciidoc b/documentation/src/main/asciidoc/chapter-2-set-up.asciidoc index 591ff0764..614be1834 100644 --- a/documentation/src/main/asciidoc/chapter-2-set-up.asciidoc +++ b/documentation/src/main/asciidoc/chapter-2-set-up.asciidoc @@ -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. diff --git a/documentation/src/main/asciidoc/chapter-4-retrieving-a-mapper.asciidoc b/documentation/src/main/asciidoc/chapter-4-retrieving-a-mapper.asciidoc index 03c2c4b55..75b5b1029 100644 --- a/documentation/src/main/asciidoc/chapter-4-retrieving-a-mapper.asciidoc +++ b/documentation/src/main/asciidoc/chapter-4-retrieving-a-mapper.asciidoc @@ -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 <>. It is recommended to use constructor injection to simplify testing. [TIP] diff --git a/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java b/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java index 5f7d3394f..d9686e963 100644 --- a/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java @@ -83,6 +83,7 @@ import static javax.lang.model.element.ElementKind.CLASS; 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 { @@ -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"; @@ -136,6 +138,7 @@ public class MappingProcessor extends AbstractProcessor { 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 ) ) ); diff --git a/processor/src/main/java/org/mapstruct/ap/internal/option/Options.java b/processor/src/main/java/org/mapstruct/ap/internal/option/Options.java index 1e555e8d3..92dcbc5dc 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/option/Options.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/option/Options.java @@ -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; } @@ -48,6 +51,10 @@ public class Options { return defaultComponentModel; } + public String getDefaultInjectionStrategy() { + return defaultInjectionStrategy; + } + public boolean isAlwaysGenerateSpi() { return alwaysGenerateSpi; } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/AnnotationBasedComponentModelProcessor.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/AnnotationBasedComponentModelProcessor.java index a4d970436..8cd9a21c0 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/processor/AnnotationBasedComponentModelProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/AnnotationBasedComponentModelProcessor.java @@ -45,7 +45,7 @@ public abstract class AnnotationBasedComponentModelProcessor implements ModelEle 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; diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/MapperConfiguration.java b/processor/src/main/java/org/mapstruct/ap/internal/util/MapperConfiguration.java index 96b25af2a..be4622c7e 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/MapperConfiguration.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/MapperConfiguration.java @@ -202,13 +202,21 @@ public class MapperConfiguration { } } - public InjectionStrategyPrism getInjectionStrategy() { - if ( mapperConfigPrism != null && mapperPrism.values.injectionStrategy() == null ) { - return InjectionStrategyPrism.valueOf( mapperConfigPrism.injectionStrategy() ); - } - else { + 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() ); + } + + 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() { diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/_default/CustomerJsr330DefaultCompileOptionFieldMapper.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/_default/CustomerJsr330DefaultCompileOptionFieldMapper.java new file mode 100644 index 000000000..9ce6b1215 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/_default/CustomerJsr330DefaultCompileOptionFieldMapper.java @@ -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); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/_default/GenderJsr330DefaultCompileOptionFieldMapper.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/_default/GenderJsr330DefaultCompileOptionFieldMapper.java new file mode 100644 index 000000000..6701ce42f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/_default/GenderJsr330DefaultCompileOptionFieldMapper.java @@ -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); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/_default/Jsr330DefaultCompileOptionFieldMapperTest.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/_default/Jsr330DefaultCompileOptionFieldMapperTest.java new file mode 100644 index 000000000..61c767869 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/_default/Jsr330DefaultCompileOptionFieldMapperTest.java @@ -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(" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/compileoptionconstructor/CustomerJsr330CompileOptionConstructorMapper.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/compileoptionconstructor/CustomerJsr330CompileOptionConstructorMapper.java new file mode 100644 index 000000000..0e89ec894 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/compileoptionconstructor/CustomerJsr330CompileOptionConstructorMapper.java @@ -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); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/compileoptionconstructor/GenderJsr330CompileOptionConstructorMapper.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/compileoptionconstructor/GenderJsr330CompileOptionConstructorMapper.java new file mode 100644 index 000000000..da3caed5b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/compileoptionconstructor/GenderJsr330CompileOptionConstructorMapper.java @@ -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); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/compileoptionconstructor/Jsr330CompileOptionConstructorMapperTest.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/compileoptionconstructor/Jsr330CompileOptionConstructorMapperTest.java new file mode 100644 index 000000000..dbf4a4f7e --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/compileoptionconstructor/Jsr330CompileOptionConstructorMapperTest.java @@ -0,0 +1,95 @@ +/* + * 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.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.compilation.annotation.ProcessorOption; +import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner; +import org.mapstruct.ap.testutil.runner.GeneratedSource; +import org.springframework.beans.factory.annotation.Autowired; +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 constructor injection for component model jsr330 with compile option + * mapstruct.defaultInjectionStrategy=constructor + * + * @author Andrei Arlou + */ +@WithClasses({ + CustomerDto.class, + CustomerEntity.class, + Gender.class, + GenderDto.class, + CustomerJsr330CompileOptionConstructorMapper.class, + GenderJsr330CompileOptionConstructorMapper.class +}) +@RunWith(AnnotationProcessorTestRunner.class) +@ProcessorOption( name = "mapstruct.defaultInjectionStrategy", value = "constructor") +@ComponentScan(basePackageClasses = CustomerJsr330CompileOptionConstructorMapper.class) +@Configuration +public class Jsr330CompileOptionConstructorMapperTest { + + @Rule + public final GeneratedSource generatedSource = new GeneratedSource(); + + @Autowired + private CustomerJsr330CompileOptionConstructorMapper 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 shouldHaveConstructorInjectionFromCompileOption() { + generatedSource.forMapper( CustomerJsr330CompileOptionConstructorMapper.class ) + .content() + .contains( "private final GenderJsr330CompileOptionConstructorMapper" ) + .contains( "@Inject" + lineSeparator() + + " public CustomerJsr330CompileOptionConstructorMapperImpl" + + "(GenderJsr330CompileOptionConstructorMapper" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/compileoptionconstructor/CustomerRecordSpringCompileOptionConstructorMapper.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/compileoptionconstructor/CustomerRecordSpringCompileOptionConstructorMapper.java new file mode 100644 index 000000000..d7debfb96 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/compileoptionconstructor/CustomerRecordSpringCompileOptionConstructorMapper.java @@ -0,0 +1,21 @@ +/* + * 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.spring.compileoptionconstructor; + +import org.mapstruct.Mapper; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerRecordDto; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerRecordEntity; + +/** + * @author Andrei Arlou + */ +@Mapper(componentModel = "spring", + uses = { CustomerSpringCompileOptionConstructorMapper.class, GenderSpringCompileOptionConstructorMapper.class }, + disableSubMappingMethodsGeneration = true) +public interface CustomerRecordSpringCompileOptionConstructorMapper { + + CustomerRecordDto asTarget(CustomerRecordEntity customerRecordEntity); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/compileoptionconstructor/CustomerSpringCompileOptionConstructorMapper.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/compileoptionconstructor/CustomerSpringCompileOptionConstructorMapper.java new file mode 100644 index 000000000..db11c1b7a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/compileoptionconstructor/CustomerSpringCompileOptionConstructorMapper.java @@ -0,0 +1,22 @@ +/* + * 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.spring.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 = "spring", + uses = GenderSpringCompileOptionConstructorMapper.class) +public interface CustomerSpringCompileOptionConstructorMapper { + + @Mapping( source = "gender", target = "gender" ) + CustomerDto asTarget(CustomerEntity customerEntity); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/compileoptionconstructor/GenderSpringCompileOptionConstructorMapper.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/compileoptionconstructor/GenderSpringCompileOptionConstructorMapper.java new file mode 100644 index 000000000..c909f1dc5 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/compileoptionconstructor/GenderSpringCompileOptionConstructorMapper.java @@ -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.spring.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 = "spring") +public interface GenderSpringCompileOptionConstructorMapper { + + @ValueMappings({ + @ValueMapping(source = "MALE", target = "M"), + @ValueMapping(source = "FEMALE", target = "F") + }) + GenderDto mapToDto(Gender gender); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/compileoptionconstructor/SpringCompileOptionConstructorMapperTest.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/compileoptionconstructor/SpringCompileOptionConstructorMapperTest.java new file mode 100644 index 000000000..7608963a6 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/compileoptionconstructor/SpringCompileOptionConstructorMapperTest.java @@ -0,0 +1,131 @@ +/* + * 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.spring.compileoptionconstructor; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.TimeZone; + +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +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.CustomerRecordDto; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerRecordEntity; +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.compilation.annotation.ProcessorOption; +import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner; +import org.mapstruct.ap.testutil.runner.GeneratedSource; +import org.springframework.beans.factory.annotation.Autowired; +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 constructor injection for component model spring with + * compile option mapstruct.defaultInjectStrategy=constructor + * + * @author Andrei Arlou + */ +@WithClasses( { + CustomerRecordDto.class, + CustomerRecordEntity.class, + CustomerDto.class, + CustomerEntity.class, + Gender.class, + GenderDto.class, + CustomerRecordSpringCompileOptionConstructorMapper.class, + CustomerSpringCompileOptionConstructorMapper.class, + GenderSpringCompileOptionConstructorMapper.class +} ) +@RunWith(AnnotationProcessorTestRunner.class) +@ProcessorOption( name = "mapstruct.defaultInjectionStrategy", value = "constructor") +@ComponentScan(basePackageClasses = CustomerSpringCompileOptionConstructorMapper.class) +@Configuration +public class SpringCompileOptionConstructorMapperTest { + + private static TimeZone originalTimeZone; + + @Rule + public final GeneratedSource generatedSource = new GeneratedSource(); + + @Autowired + private CustomerRecordSpringCompileOptionConstructorMapper customerRecordMapper; + private ConfigurableApplicationContext context; + + @BeforeClass + public static void setDefaultTimeZoneToCet() { + originalTimeZone = TimeZone.getDefault(); + TimeZone.setDefault( TimeZone.getTimeZone( "Europe/Berlin" ) ); + } + + @AfterClass + public static void restoreOriginalTimeZone() { + TimeZone.setDefault( originalTimeZone ); + } + + @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() throws Exception { + // given + CustomerEntity customerEntity = new CustomerEntity(); + customerEntity.setName( "Samuel" ); + customerEntity.setGender( Gender.MALE ); + CustomerRecordEntity customerRecordEntity = new CustomerRecordEntity(); + customerRecordEntity.setCustomer( customerEntity ); + customerRecordEntity.setRegistrationDate( createDate( "31-08-1982 10:20:56" ) ); + + // when + CustomerRecordDto customerRecordDto = customerRecordMapper.asTarget( customerRecordEntity ); + + // then + assertThat( customerRecordDto ).isNotNull(); + assertThat( customerRecordDto.getCustomer() ).isNotNull(); + assertThat( customerRecordDto.getCustomer().getName() ).isEqualTo( "Samuel" ); + assertThat( customerRecordDto.getCustomer().getGender() ).isEqualTo( GenderDto.M ); + assertThat( customerRecordDto.getRegistrationDate() ).isNotNull(); + assertThat( customerRecordDto.getRegistrationDate().toString() ).isEqualTo( "1982-08-31T10:20:56.000+02:00" ); + } + + private Date createDate(String date) throws ParseException { + SimpleDateFormat sdf = new SimpleDateFormat( "dd-M-yyyy hh:mm:ss" ); + return sdf.parse( date ); + } + + @Test + public void shouldConstructorInjectionFromCompileOption() { + generatedSource.forMapper( CustomerSpringCompileOptionConstructorMapper.class ) + .content() + .contains( "private final GenderSpringCompileOptionConstructorMapper" ) + .contains( "@Autowired" + lineSeparator() + + " public CustomerSpringCompileOptionConstructorMapperImpl" + + "(GenderSpringCompileOptionConstructorMapper" ); + } +}