diff --git a/build.gradle b/build.gradle index f30e1de..4313dcd 100644 --- a/build.gradle +++ b/build.gradle @@ -63,9 +63,12 @@ subprojects { } } - test { - // Use junit platform for unit tests - useJUnitPlatform() + testing { + suites { + test { + useJUnitJupiter() + } + } } jacoco { diff --git a/extensions/src/main/java/org/mapstruct/extensions/spring/converter/ConversionServiceAdapterGenerator.java b/extensions/src/main/java/org/mapstruct/extensions/spring/converter/ConversionServiceAdapterGenerator.java index ee8b666..cd745c8 100644 --- a/extensions/src/main/java/org/mapstruct/extensions/spring/converter/ConversionServiceAdapterGenerator.java +++ b/extensions/src/main/java/org/mapstruct/extensions/spring/converter/ConversionServiceAdapterGenerator.java @@ -1,6 +1,7 @@ package org.mapstruct.extensions.spring.converter; import static java.lang.Boolean.TRUE; +import static java.lang.Boolean.parseBoolean; import static java.time.format.DateTimeFormatter.ISO_INSTANT; import static java.util.stream.Collectors.toList; import static java.util.stream.Stream.concat; @@ -51,6 +52,7 @@ public class ConversionServiceAdapterGenerator { JAVA_9_PLUS_ANNOTATION_GENERATED_PACKAGE, GENERATED_ANNOTATION_CLASS_NAME_STRING); private static final ClassName COMPONENT_ANNOTATION_CLASS_NAME = ClassName.get("org.springframework.stereotype", "Component"); + private static final String SUPPRESS_GENERATOR_TIMESTAMP = "mapstruct.suppressGeneratorTimestamp"; private final Clock clock; private final AtomicReference processingEnvironment; @@ -60,11 +62,10 @@ public ConversionServiceAdapterGenerator(final Clock clock) { processingEnvironment = new AtomicReference<>(); } - ProcessingEnvironment getProcessingEnvironment() { return processingEnvironment.get(); } - + public void writeConversionServiceAdapter( final ConversionServiceAdapterDescriptor descriptor, final Writer out) { try { @@ -141,10 +142,11 @@ private String collectionOfMethodName(final ParameterizedTypeName parameterizedT return simpleName(parameterizedTypeName); } - private boolean isCollectionWithGenericParameter(final ParameterizedTypeName parameterizedTypeName) { + private boolean isCollectionWithGenericParameter( + final ParameterizedTypeName parameterizedTypeName) { return parameterizedTypeName.typeArguments != null - && parameterizedTypeName.typeArguments.size() > 0 - && isCollection(parameterizedTypeName); + && parameterizedTypeName.typeArguments.size() > 0 + && isCollection(parameterizedTypeName); } private boolean isCollection(final ParameterizedTypeName parameterizedTypeName) { @@ -194,8 +196,8 @@ private static TypeName rawType(final TypeName typeName) { } private Iterable buildMappingMethods( - final ConversionServiceAdapterDescriptor descriptor, - final FieldSpec injectedConversionServiceFieldSpec) { + final ConversionServiceAdapterDescriptor descriptor, + final FieldSpec injectedConversionServiceFieldSpec) { return descriptor.getFromToMappings().stream() .map( sourceTargetPair -> @@ -204,8 +206,8 @@ private Iterable buildMappingMethods( } private MethodSpec toMappingMethodSpec( - final FieldSpec injectedConversionServiceFieldSpec, - final Pair sourceTargetPair) { + final FieldSpec injectedConversionServiceFieldSpec, + final Pair sourceTargetPair) { final ParameterSpec sourceParameterSpec = buildSourceParameterSpec(sourceTargetPair.getLeft()); return MethodSpec.methodBuilder( String.format( @@ -220,7 +222,8 @@ private MethodSpec toMappingMethodSpec( "return ($T) $N.convert($N, %s, %s)", typeDescriptorFormat(sourceTargetPair.getLeft()), typeDescriptorFormat(sourceTargetPair.getRight())), - allTypeDescriptorArguments(injectedConversionServiceFieldSpec, sourceParameterSpec, sourceTargetPair)) + allTypeDescriptorArguments( + injectedConversionServiceFieldSpec, sourceParameterSpec, sourceTargetPair)) .build(); } @@ -241,10 +244,10 @@ private Object[] allTypeDescriptorArguments( private String typeDescriptorFormat(final TypeName typeName) { if (typeName instanceof ParameterizedTypeName - && isCollectionWithGenericParameter((ParameterizedTypeName) typeName)) { + && isCollectionWithGenericParameter((ParameterizedTypeName) typeName)) { return String.format( - "$T.collection($T.class, %s)", - typeDescriptorFormat(((ParameterizedTypeName) typeName).typeArguments.iterator().next())); + "$T.collection($T.class, %s)", + typeDescriptorFormat(((ParameterizedTypeName) typeName).typeArguments.iterator().next())); } return "$T.valueOf($T.class)"; } @@ -274,11 +277,19 @@ private AnnotationSpec buildGeneratedAnnotationSpec() { .map( build -> build.addMember("value", "$S", ConversionServiceAdapterGenerator.class.getName())) - .map(build -> build.addMember("date", "$S", ISO_INSTANT.format(ZonedDateTime.now(clock)))) + .map(this::addDateIfNotSuppressed) .map(AnnotationSpec.Builder::build) .orElse(null); } + private AnnotationSpec.Builder addDateIfNotSuppressed( + final AnnotationSpec.Builder generatedAnnotationSpecBuilder) { + return parseBoolean(processingEnvironment.get().getOptions().get(SUPPRESS_GENERATOR_TIMESTAMP)) + ? generatedAnnotationSpecBuilder + : generatedAnnotationSpecBuilder.addMember( + "date", "$S", ISO_INSTANT.format(ZonedDateTime.now(clock))); + } + private AnnotationSpec.Builder baseAnnotationSpecBuilder() { final AnnotationSpec.Builder builder; if (isJava9PlusGeneratedAvailable()) { @@ -296,8 +307,7 @@ private boolean isPreJava9GeneratedAvailable() { } private boolean isJava9PlusGeneratedAvailable() { - return isSourceVersionAtLeast9() - && isTypeAvailable(JAVA_9_PLUS_ANNOTATION_GENERATED); + return isSourceVersionAtLeast9() && isTypeAvailable(JAVA_9_PLUS_ANNOTATION_GENERATED); } private boolean isSourceVersionAtLeast9() { diff --git a/extensions/src/test/java/org/mapstruct/extensions/spring/converter/ConversionServiceAdapterGeneratorTest.java b/extensions/src/test/java/org/mapstruct/extensions/spring/converter/ConversionServiceAdapterGeneratorTest.java index 2b56c4f..2a00866 100644 --- a/extensions/src/test/java/org/mapstruct/extensions/spring/converter/ConversionServiceAdapterGeneratorTest.java +++ b/extensions/src/test/java/org/mapstruct/extensions/spring/converter/ConversionServiceAdapterGeneratorTest.java @@ -1,6 +1,8 @@ package org.mapstruct.extensions.spring.converter; +import static java.lang.Boolean.TRUE; import static java.nio.charset.StandardCharsets.UTF_8; +import static java.util.function.UnaryOperator.identity; import static javax.lang.model.SourceVersion.RELEASE_8; import static javax.lang.model.SourceVersion.RELEASE_9; import static org.apache.commons.io.IOUtils.resourceToString; @@ -17,6 +19,8 @@ import java.time.ZoneId; import java.time.ZonedDateTime; import java.util.List; +import java.util.Map; +import java.util.function.UnaryOperator; import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.SourceVersion; import javax.lang.model.element.TypeElement; @@ -46,22 +50,24 @@ class ConversionServiceAdapterGeneratorTest { @Nested class DefaultProcessingEnvironment { + @Mock private ProcessingEnvironment processingEnvironment; + @BeforeEach void initWithProcessingEnvironment() { - final var processingEnvironment = mock(ProcessingEnvironment.class); given(processingEnvironment.getElementUtils()).willReturn(elements); given(processingEnvironment.getSourceVersion()) - .will((Answer) - (invocation) -> { - if (isAtLeastJava9) { - return RELEASE_9; - } else { - return RELEASE_8; - } - }); + .will( + (Answer) + (invocation) -> { + if (isAtLeastJava9) { + return RELEASE_9; + } else { + return RELEASE_8; + } + }); underTest.init(processingEnvironment); } - + @Nested class Java8Generated { @BeforeEach @@ -83,6 +89,15 @@ void shouldGenerateMatchingOutputWhenUsingCustomConversionService() throws IOExc .shouldGenerateMatchingOutputWhenUsingCustomConversionService( "ConversionServiceAdapterCustomBeanJava8Generated.java"); } + + @Test + void shouldSuppressDateGenerationWhenProcessingEnvironmentHasSuppressionSetToTrue() + throws IOException { + given(processingEnvironment.getOptions()) + .willReturn(Map.of("mapstruct.suppressGeneratorTimestamp", String.valueOf(TRUE))); + ConversionServiceAdapterGeneratorTest.this.shouldGenerateMatchingOutput( + "ConversionServiceAdapterJava8GeneratedNoDate.java"); + } } @Nested @@ -106,6 +121,15 @@ void shouldGenerateMatchingOutputWhenUsingCustomConversionService() throws IOExc .shouldGenerateMatchingOutputWhenUsingCustomConversionService( "ConversionServiceAdapterCustomBeanJava9PlusGenerated.java"); } + + @Test + void shouldSuppressDateGenerationWhenProcessingEnvironmentHasSuppressionSetToTrue() + throws IOException { + given(processingEnvironment.getOptions()) + .willReturn(Map.of("mapstruct.suppressGeneratorTimestamp", String.valueOf(TRUE))); + ConversionServiceAdapterGeneratorTest.this.shouldGenerateMatchingOutput( + "ConversionServiceAdapterJava9PlusGeneratedNoDate.java"); + } } @Nested @@ -130,23 +154,26 @@ void shouldGenerateMatchingOutputWhenUsingCustomConversionService() throws IOExc } } - void shouldGenerateMatchingOutput(final String expectedContentFileName) throws IOException { - // Given + void shouldGenerateMatchingOutput( + final String expectedContentFileName, + final UnaryOperator descriptorDecorator) + throws IOException { final ConversionServiceAdapterDescriptor descriptor = - new ConversionServiceAdapterDescriptor() - .adapterClassName( - ClassName.get( - ConversionServiceAdapterGeneratorTest.class.getPackage().getName(), - "ConversionServiceAdapter")) - .fromToMappings( - List.of( - Pair.of(ClassName.get("test", "Car"), ClassName.get("test", "CarDto")), - Pair.of( - ParameterizedTypeName.get( - ClassName.get(List.class), ClassName.get("test", "Car")), - ParameterizedTypeName.get( - ClassName.get(List.class), ClassName.get("test", "CarDto"))))) - .lazyAnnotatedConversionServiceBean(true); + descriptorDecorator.apply( + new ConversionServiceAdapterDescriptor() + .adapterClassName( + ClassName.get( + ConversionServiceAdapterGeneratorTest.class.getPackage().getName(), + "ConversionServiceAdapter")) + .fromToMappings( + List.of( + Pair.of(ClassName.get("test", "Car"), ClassName.get("test", "CarDto")), + Pair.of( + ParameterizedTypeName.get( + ClassName.get(List.class), ClassName.get("test", "Car")), + ParameterizedTypeName.get( + ClassName.get(List.class), ClassName.get("test", "CarDto"))))) + .lazyAnnotatedConversionServiceBean(true)); final StringWriter outputWriter = new StringWriter(); // When @@ -157,33 +184,15 @@ void shouldGenerateMatchingOutput(final String expectedContentFileName) throws I .isEqualToIgnoringWhitespace(resourceToString('/' + expectedContentFileName, UTF_8)); } + void shouldGenerateMatchingOutput(final String expectedContentFileName) throws IOException { + shouldGenerateMatchingOutput(expectedContentFileName, identity()); + } + void shouldGenerateMatchingOutputWhenUsingCustomConversionService( final String expectedContentFileName) throws IOException { - // Given - final ConversionServiceAdapterDescriptor descriptor = - new ConversionServiceAdapterDescriptor() - .adapterClassName( - ClassName.get( - ConversionServiceAdapterGeneratorTest.class.getPackage().getName(), - "ConversionServiceAdapter")) - .conversionServiceBeanName("myConversionService") - .fromToMappings( - List.of( - Pair.of(ClassName.get("test", "Car"), ClassName.get("test", "CarDto")), - Pair.of( - ParameterizedTypeName.get( - ClassName.get(List.class), ClassName.get("test", "Car")), - ParameterizedTypeName.get( - ClassName.get(List.class), ClassName.get("test", "CarDto"))))) - .lazyAnnotatedConversionServiceBean(true); - final StringWriter outputWriter = new StringWriter(); - - // When - underTest.writeConversionServiceAdapter(descriptor, outputWriter); - - // Then - then(outputWriter.toString()) - .isEqualToIgnoringWhitespace(resourceToString('/' + expectedContentFileName, UTF_8)); + shouldGenerateMatchingOutput( + expectedContentFileName, + descriptor -> descriptor.conversionServiceBeanName("myConversionService")); } @Nested diff --git a/extensions/src/test/resources/ConversionServiceAdapterJava8GeneratedNoDate.java b/extensions/src/test/resources/ConversionServiceAdapterJava8GeneratedNoDate.java new file mode 100644 index 0000000..6351e6b --- /dev/null +++ b/extensions/src/test/resources/ConversionServiceAdapterJava8GeneratedNoDate.java @@ -0,0 +1,28 @@ +package org.mapstruct.extensions.spring.converter; + +import java.util.List; +import javax.annotation.Generated; +import org.springframework.context.annotation.Lazy; +import org.springframework.core.convert.ConversionService; +import org.springframework.core.convert.TypeDescriptor; +import org.springframework.stereotype.Component; +import test.Car; +import test.CarDto; + +@Generated("org.mapstruct.extensions.spring.converter.ConversionServiceAdapterGenerator") +@Component +public class ConversionServiceAdapter { + private final ConversionService conversionService; + + public ConversionServiceAdapter(@Lazy final ConversionService conversionService) { + this.conversionService = conversionService; + } + + public CarDto mapCarToCarDto(final Car source) { + return (CarDto) conversionService.convert(source, TypeDescriptor.valueOf(Car.class), TypeDescriptor.valueOf(CarDto.class)); + } + + public List mapListOfCarToListOfCarDto(final List source) { + return (List) conversionService.convert(source, TypeDescriptor.collection(List.class, TypeDescriptor.valueOf(Car.class)), TypeDescriptor.collection(List.class, TypeDescriptor.valueOf(CarDto.class))); + } +} diff --git a/extensions/src/test/resources/ConversionServiceAdapterJava9PlusGeneratedNoDate.java b/extensions/src/test/resources/ConversionServiceAdapterJava9PlusGeneratedNoDate.java new file mode 100644 index 0000000..d675362 --- /dev/null +++ b/extensions/src/test/resources/ConversionServiceAdapterJava9PlusGeneratedNoDate.java @@ -0,0 +1,28 @@ +package org.mapstruct.extensions.spring.converter; + +import java.util.List; +import javax.annotation.processing.Generated; +import org.springframework.context.annotation.Lazy; +import org.springframework.core.convert.ConversionService; +import org.springframework.core.convert.TypeDescriptor; +import org.springframework.stereotype.Component; +import test.Car; +import test.CarDto; + +@Generated("org.mapstruct.extensions.spring.converter.ConversionServiceAdapterGenerator") +@Component +public class ConversionServiceAdapter { + private final ConversionService conversionService; + + public ConversionServiceAdapter(@Lazy final ConversionService conversionService) { + this.conversionService = conversionService; + } + + public CarDto mapCarToCarDto(final Car source) { + return (CarDto) conversionService.convert(source, TypeDescriptor.valueOf(Car.class), TypeDescriptor.valueOf(CarDto.class)); + } + + public List mapListOfCarToListOfCarDto(final List source) { + return (List) conversionService.convert(source, TypeDescriptor.collection(List.class, TypeDescriptor.valueOf(Car.class)), TypeDescriptor.collection(List.class, TypeDescriptor.valueOf(CarDto.class))); + } +}