diff --git a/build.gradle b/build.gradle index 5745964..8803ce3 100644 --- a/build.gradle +++ b/build.gradle @@ -55,7 +55,7 @@ subprojects { apply plugin: "jacoco" group = 'org.mapstruct.extensions.spring' - version = '1.0.0' + version = '1.0.1' java { toolchain { @@ -69,7 +69,7 @@ subprojects { } jacoco { - toolVersion = "0.8.8" + toolVersion = "0.8.9" } jacocoTestReport { diff --git a/docs/src/docs/asciidoc/chapter-2-set-up.asciidoc b/docs/src/docs/asciidoc/chapter-2-set-up.asciidoc index e719cdf..f8b062a 100644 --- a/docs/src/docs/asciidoc/chapter-2-set-up.asciidoc +++ b/docs/src/docs/asciidoc/chapter-2-set-up.asciidoc @@ -1,7 +1,7 @@ [[setup]] == Set up -MapStruct Spring Extensions is a Java annotation processor based on http://www.jcp.org/en/jsr/detail?id=269[JSR 269] and as such can be used within command line builds (javac, Ant, Maven etc.) as well as from within your IDE. +MapStruct Spring Extensions is a Java annotation processor based on http://www.jcp.org/en/jsr/detail?id=269[JSR 269] and as such can be used within command line builds (javac, Ant, Maven etc.) as well as from within your IDE. The minimum JDK version is 11. IMPORTANT: Also, you will need MapStruct itself (at least version `1.4.0.Final`) in your project. diff --git a/docs/src/docs/asciidoc/chapter-3-mapper-as-converter.asciidoc b/docs/src/docs/asciidoc/chapter-3-mapper-as-converter.asciidoc index 7d6fb1c..5c0365e 100644 --- a/docs/src/docs/asciidoc/chapter-3-mapper-as-converter.asciidoc +++ b/docs/src/docs/asciidoc/chapter-3-mapper-as-converter.asciidoc @@ -29,7 +29,7 @@ This allows using the Mapper indirectly via the `ConversionService`: ---- ==== -All this can be achieved already with MapStruct's core functionality. However, when a Mapper wants to https://mapstruct.org/documentation/stable/reference/html/#invoking-other-mappers[invoke] another one, it can't take the route via the `ConversionService`, because the latter's `convert` method does not match the signature that MapStruct expects for a mapping method. Thus, the developer still has to add every invoked Mapper to the invoking Mapper's `uses` element. This creates (aside from a potentially long list) a tight coupling between Mappers that the `ConversionService` wants to avoid. +All this can be achieved already with MapStruct's core functionality. However, when a Mapper wants to https://mapstruct.org/documentation/stable/reference/html/#invoking-other-mappers[invoke] another one, it can't take the route via the `ConversionService`, because the latter's `convert` method does not match the signature that MapStruct expects for a mapping method. Thus, the developer still has to add every invoked Mapper to the invoking Mapper's `uses` element. This creates (aside from a potentially long list) a tight coupling between Mappers that the `ConversionService` is designed to avoid. This is where MapStruct Spring Extensions can help. Including the two artifacts in your build will generate an Adapter class that _can_ be used by an invoking Mapper. Let's say that the above CarMapper is accompanied by a SeatConfigurationMapper: @@ -61,12 +61,12 @@ public class ConversionServiceAdapter { } public CarDto mapCarToCarDto(final Car source) { - return conversionService.convert(source, CarDto.class); + return (CarDto) conversionService.convert(source, TypeDescriptor.valueOf(Car.class), TypeDescriptor.valueOf(CarDto.class)); } public SeatConfigurationDto mapSeatConfigurationToSeatConfigurationDto( final SeatConfiguration source) { - return conversionService.convert(source, SeatConfigurationDto.class); + return (SeatConfigurationDto) conversionService.convert(source, TypeDescriptor.valueOf(SeatConfiguration.class), TypeDescriptor.valueOf(SeatConfigurationDto.class)); } } ---- @@ -165,7 +165,7 @@ public class ConversionServiceAdapter { } public Locale mapStringToLocale(final String source) { - return conversionService.convert(source, Locale.class); + return (Locale) conversionService.convert(source, TypeDescriptor.valueOf(String.class), TypeDescriptor.valueOf(Locale.class)); } } ---- diff --git a/examples/arrays/src/test/java/org/mapstruct/extensions/spring/converter/ConversionServiceAdapterTest.java b/examples/arrays/src/test/java/org/mapstruct/extensions/spring/converter/ConversionServiceAdapterTest.java index c02bb20..7530f7d 100644 --- a/examples/arrays/src/test/java/org/mapstruct/extensions/spring/converter/ConversionServiceAdapterTest.java +++ b/examples/arrays/src/test/java/org/mapstruct/extensions/spring/converter/ConversionServiceAdapterTest.java @@ -1,16 +1,16 @@ package org.mapstruct.extensions.spring.converter; +import static org.mockito.BDDMockito.then; +import static org.mockito.Mockito.mock; + +import java.sql.Blob; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.core.convert.ConversionService; - -import java.sql.Blob; - -import static org.mockito.BDDMockito.then; -import static org.mockito.Mockito.mock; +import org.springframework.core.convert.TypeDescriptor; @ExtendWith(MockitoExtension.class) class ConversionServiceAdapterTest { @@ -29,6 +29,6 @@ void shouldMapViaConversionServiceInGeneratedMethod() { conversionServiceAdapter.mapBlobToArrayOfbyte(blob); // Then - then(conversionService).should().convert(blob, byte[].class); + then(conversionService).should().convert(blob, TypeDescriptor.valueOf(Blob.class), TypeDescriptor.valueOf(byte[].class)); } } diff --git a/examples/classname/src/test/java/org/mapstruct/extensions/spring/example/ConversionServiceAdapterIntegrationTest.java b/examples/classname/src/test/java/org/mapstruct/extensions/spring/example/ConversionServiceAdapterIntegrationTest.java index f4732dd..817d852 100644 --- a/examples/classname/src/test/java/org/mapstruct/extensions/spring/example/ConversionServiceAdapterIntegrationTest.java +++ b/examples/classname/src/test/java/org/mapstruct/extensions/spring/example/ConversionServiceAdapterIntegrationTest.java @@ -7,6 +7,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; +import org.springframework.core.convert.TypeDescriptor; import org.springframework.core.convert.support.ConfigurableConversionService; import org.springframework.core.convert.support.DefaultConversionService; import org.springframework.stereotype.Component; @@ -64,6 +65,10 @@ void shouldKnowAllMappers() { then(conversionService.canConvert(Wheel.class, WheelDto.class)).isTrue(); then(conversionService.canConvert(Wheels.class, List.class)).isTrue(); then(conversionService.canConvert(List.class, Wheels.class)).isTrue(); + then(conversionService.canConvert( + TypeDescriptor.collection(List.class, TypeDescriptor.valueOf(WheelDto.class)), + TypeDescriptor.valueOf((Wheels.class)))) + .isTrue(); } @Test diff --git a/examples/custom-conversion-service-bean/src/test/java/org/mapstruct/extensions/spring/example/ConversionServiceAdapterIntegrationTest.java b/examples/custom-conversion-service-bean/src/test/java/org/mapstruct/extensions/spring/example/ConversionServiceAdapterIntegrationTest.java index d79348d..c4a47cd 100644 --- a/examples/custom-conversion-service-bean/src/test/java/org/mapstruct/extensions/spring/example/ConversionServiceAdapterIntegrationTest.java +++ b/examples/custom-conversion-service-bean/src/test/java/org/mapstruct/extensions/spring/example/ConversionServiceAdapterIntegrationTest.java @@ -8,6 +8,7 @@ import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; +import org.springframework.core.convert.TypeDescriptor; import org.springframework.core.convert.support.ConfigurableConversionService; import org.springframework.core.convert.support.DefaultConversionService; import org.springframework.stereotype.Component; @@ -73,6 +74,10 @@ void shouldKnowAllMappers() { then(conversionService.canConvert(Wheel.class, WheelDto.class)).isTrue(); then(conversionService.canConvert(Wheels.class, List.class)).isTrue(); then(conversionService.canConvert(List.class, Wheels.class)).isTrue(); + then(conversionService.canConvert( + TypeDescriptor.collection(List.class, TypeDescriptor.valueOf(WheelDto.class)), + TypeDescriptor.valueOf((Wheels.class)))) + .isTrue(); } @Test diff --git a/examples/noconfig/src/test/java/org/mapstruct/extensions/spring/example/ConversionServiceAdapterIntegrationTest.java b/examples/noconfig/src/test/java/org/mapstruct/extensions/spring/example/ConversionServiceAdapterIntegrationTest.java index e434363..23bb0ea 100644 --- a/examples/noconfig/src/test/java/org/mapstruct/extensions/spring/example/ConversionServiceAdapterIntegrationTest.java +++ b/examples/noconfig/src/test/java/org/mapstruct/extensions/spring/example/ConversionServiceAdapterIntegrationTest.java @@ -7,6 +7,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; +import org.springframework.core.convert.TypeDescriptor; import org.springframework.core.convert.support.ConfigurableConversionService; import org.springframework.core.convert.support.DefaultConversionService; import org.springframework.stereotype.Component; @@ -64,6 +65,10 @@ void shouldKnowAllMappers() { then(conversionService.canConvert(Wheel.class, WheelDto.class)).isTrue(); then(conversionService.canConvert(Wheels.class, List.class)).isTrue(); then(conversionService.canConvert(List.class, Wheels.class)).isTrue(); + then(conversionService.canConvert( + TypeDescriptor.collection(List.class, TypeDescriptor.valueOf(WheelDto.class)), + TypeDescriptor.valueOf((Wheels.class)))) + .isTrue(); } @Test diff --git a/examples/packagename-and-classname/src/test/java/org/mapstruct/extensions/spring/example/ConversionServiceAdapterIntegrationTest.java b/examples/packagename-and-classname/src/test/java/org/mapstruct/extensions/spring/example/ConversionServiceAdapterIntegrationTest.java index 58b554a..a02718a 100644 --- a/examples/packagename-and-classname/src/test/java/org/mapstruct/extensions/spring/example/ConversionServiceAdapterIntegrationTest.java +++ b/examples/packagename-and-classname/src/test/java/org/mapstruct/extensions/spring/example/ConversionServiceAdapterIntegrationTest.java @@ -7,6 +7,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; +import org.springframework.core.convert.TypeDescriptor; import org.springframework.core.convert.support.ConfigurableConversionService; import org.springframework.core.convert.support.DefaultConversionService; import org.springframework.stereotype.Component; @@ -64,6 +65,10 @@ void shouldKnowAllMappers() { then(conversionService.canConvert(Wheel.class, WheelDto.class)).isTrue(); then(conversionService.canConvert(Wheels.class, List.class)).isTrue(); then(conversionService.canConvert(List.class, Wheels.class)).isTrue(); + then(conversionService.canConvert( + TypeDescriptor.collection(List.class, TypeDescriptor.valueOf(WheelDto.class)), + TypeDescriptor.valueOf((Wheels.class)))) + .isTrue(); } @Test diff --git a/examples/packagename/src/test/java/org/mapstruct/extensions/spring/example/ConversionServiceAdapterIntegrationTest.java b/examples/packagename/src/test/java/org/mapstruct/extensions/spring/example/ConversionServiceAdapterIntegrationTest.java index 43a226b..05a18ed 100644 --- a/examples/packagename/src/test/java/org/mapstruct/extensions/spring/example/ConversionServiceAdapterIntegrationTest.java +++ b/examples/packagename/src/test/java/org/mapstruct/extensions/spring/example/ConversionServiceAdapterIntegrationTest.java @@ -7,6 +7,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; +import org.springframework.core.convert.TypeDescriptor; import org.springframework.core.convert.support.ConfigurableConversionService; import org.springframework.core.convert.support.DefaultConversionService; import org.springframework.stereotype.Component; @@ -60,10 +61,15 @@ void addMappersToConversionService() { @Test void shouldKnowAllMappers() { then(conversionService.canConvert(Car.class, CarDto.class)).isTrue(); - then(conversionService.canConvert(SeatConfiguration.class, SeatConfigurationDto.class)).isTrue(); + then(conversionService.canConvert(SeatConfiguration.class, SeatConfigurationDto.class)) + .isTrue(); then(conversionService.canConvert(Wheel.class, WheelDto.class)).isTrue(); then(conversionService.canConvert(Wheels.class, List.class)).isTrue(); then(conversionService.canConvert(List.class, Wheels.class)).isTrue(); + then(conversionService.canConvert( + TypeDescriptor.collection(List.class, TypeDescriptor.valueOf(WheelDto.class)), + TypeDescriptor.valueOf((Wheels.class)))) + .isTrue(); } @Test diff --git a/extensions/src/main/java/org/mapstruct/extensions/spring/converter/ConversionServiceAdapterDescriptor.java b/extensions/src/main/java/org/mapstruct/extensions/spring/converter/ConversionServiceAdapterDescriptor.java index ed0c816..180275d 100644 --- a/extensions/src/main/java/org/mapstruct/extensions/spring/converter/ConversionServiceAdapterDescriptor.java +++ b/extensions/src/main/java/org/mapstruct/extensions/spring/converter/ConversionServiceAdapterDescriptor.java @@ -2,10 +2,8 @@ import com.squareup.javapoet.ClassName; import com.squareup.javapoet.TypeName; -import org.apache.commons.lang3.tuple.Pair; - -import javax.lang.model.util.Elements; import java.util.List; +import org.apache.commons.lang3.tuple.Pair; public class ConversionServiceAdapterDescriptor { private ClassName adapterClassName; @@ -13,27 +11,6 @@ public class ConversionServiceAdapterDescriptor { private List> fromToMappings; private boolean lazyAnnotatedConversionServiceBean; - public Elements getElementUtils() { - return elementUtils; - } - - public ConversionServiceAdapterDescriptor elementUtils(Elements elementUtils) { - this.elementUtils = elementUtils; - return this; - } - - private Elements elementUtils; - public boolean isSourceVersionAtLeast9() { - return sourceVersionAtLeast9; - } - - public ConversionServiceAdapterDescriptor sourceVersionAtLeast9(boolean sourceVersionAtLeast9) { - this.sourceVersionAtLeast9 = sourceVersionAtLeast9; - return this; - } - - private boolean sourceVersionAtLeast9; - public ClassName getAdapterClassName() { return adapterClassName; } 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 7237417..ee8b666 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,37 +1,70 @@ package org.mapstruct.extensions.spring.converter; -import com.squareup.javapoet.*; -import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.tuple.Pair; +import static java.lang.Boolean.TRUE; +import static java.time.format.DateTimeFormatter.ISO_INSTANT; +import static java.util.stream.Collectors.toList; +import static java.util.stream.Stream.concat; +import static javax.lang.model.SourceVersion.RELEASE_8; +import static javax.lang.model.element.Modifier.*; +import static javax.tools.Diagnostic.Kind.WARNING; +import static org.apache.commons.lang3.StringUtils.isNotEmpty; +import com.squareup.javapoet.*; import java.io.IOException; import java.io.UncheckedIOException; import java.io.Writer; import java.time.Clock; import java.time.ZonedDateTime; +import java.util.Collection; import java.util.Optional; - -import static java.time.format.DateTimeFormatter.ISO_INSTANT; -import static java.util.stream.Collectors.toList; -import static javax.lang.model.element.Modifier.*; +import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Stream; +import javax.annotation.processing.ProcessingEnvironment; +import org.apache.commons.lang3.tuple.Pair; public class ConversionServiceAdapterGenerator { - private static final String CONVERSION_SERVICE_PACKAGE_NAME = "org.springframework.core.convert"; - private static final String CONVERSION_SERVICE_CLASS_NAME = "ConversionService"; + private static final ClassName CONVERSION_SERVICE_CLASS_NAME = + ClassName.get("org.springframework.core.convert", "ConversionService"); private static final String CONVERSION_SERVICE_FIELD_NAME = "conversionService"; - private static final String QUALIFIER_ANNOTATION_PACKAGE_NAME = - "org.springframework.beans.factory.annotation"; - private static final String QUALIFIER_ANNOTATION_CLASSS_NAME = "Qualifier"; - private static final String LAZY_ANNOTATION_PACKAGE_NAME = - "org.springframework.context.annotation"; - private static final String LAZY_ANNOTATION_CLASS_NAME = "Lazy"; - + private static final ClassName QUALIFIER_ANNOTATION_CLASS_NAME = + ClassName.get("org.springframework.beans.factory.annotation", "Qualifier"); + private static final ClassName LAZY_ANNOTATION_CLASS_NAME = + ClassName.get("org.springframework.context.annotation", "Lazy"); + private static final ClassName TYPE_DESCRIPTOR_CLASS_NAME = + ClassName.get("org.springframework.core.convert", "TypeDescriptor"); + private static final String GENERATED_ANNOTATION_CLASS_NAME_STRING = "Generated"; + private static final String PRE_JAVA_9_ANNOTATION_GENERATED_PACKAGE = "javax.annotation"; + private static final ClassName PRE_JAVA_9_ANNOTATION_GENERATED_CLASS_NAME = + ClassName.get( + PRE_JAVA_9_ANNOTATION_GENERATED_PACKAGE, GENERATED_ANNOTATION_CLASS_NAME_STRING); + private static final String JAVA_9_PLUS_ANNOTATION_GENERATED_PACKAGE = + "javax.annotation.processing"; + private static final ClassName JAVA_9_PLUS_ANNOTATION_GENERATED_CLASS_NAME = + ClassName.get( + JAVA_9_PLUS_ANNOTATION_GENERATED_PACKAGE, GENERATED_ANNOTATION_CLASS_NAME_STRING); + private static final String PRE_JAVA_9_ANNOTATION_GENERATED = + String.format( + "%s.%s", PRE_JAVA_9_ANNOTATION_GENERATED_PACKAGE, GENERATED_ANNOTATION_CLASS_NAME_STRING); + private static final String JAVA_9_PLUS_ANNOTATION_GENERATED = + String.format( + "%s.%s", + 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 final Clock clock; + private final AtomicReference processingEnvironment; + public ConversionServiceAdapterGenerator(final Clock clock) { this.clock = clock; + processingEnvironment = new AtomicReference<>(); } + + ProcessingEnvironment getProcessingEnvironment() { + return processingEnvironment.get(); + } + public void writeConversionServiceAdapter( final ConversionServiceAdapterDescriptor descriptor, final Writer out) { try { @@ -50,10 +83,10 @@ private TypeSpec createConversionServiceTypeSpec( final FieldSpec conversionServiceFieldSpec = buildConversionServiceFieldSpec(); final TypeSpec.Builder adapterClassTypeSpec = TypeSpec.classBuilder(descriptor.getAdapterClassName()).addModifiers(PUBLIC); - Optional.ofNullable(buildGeneratedAnnotationSpec(descriptor)) + Optional.ofNullable(buildGeneratedAnnotationSpec()) .ifPresent(adapterClassTypeSpec::addAnnotation); return adapterClassTypeSpec - .addAnnotation(ClassName.get("org.springframework.stereotype", "Component")) + .addAnnotation(COMPONENT_ANNOTATION_CLASS_NAME) .addField(conversionServiceFieldSpec) .addMethod(buildConstructorSpec(descriptor, conversionServiceFieldSpec)) .addMethods(buildMappingMethods(descriptor, conversionServiceFieldSpec)) @@ -78,27 +111,63 @@ private static ParameterSpec buildConstructorParameterSpec( final ParameterSpec.Builder parameterBuilder = ParameterSpec.builder( conversionServiceFieldSpec.type, conversionServiceFieldSpec.name, FINAL); - if (StringUtils.isNotEmpty(descriptor.getConversionServiceBeanName())) { + if (isNotEmpty(descriptor.getConversionServiceBeanName())) { parameterBuilder.addAnnotation(buildQualifierAnnotation(descriptor)); } - if (Boolean.TRUE.equals(descriptor.isLazyAnnotatedConversionServiceBean())) { + if (TRUE.equals(descriptor.isLazyAnnotatedConversionServiceBean())) { parameterBuilder.addAnnotation(buildLazyAnnotation()); } return parameterBuilder.build(); } private static AnnotationSpec buildQualifierAnnotation( - ConversionServiceAdapterDescriptor descriptor) { - return AnnotationSpec.builder( - ClassName.get(QUALIFIER_ANNOTATION_PACKAGE_NAME, QUALIFIER_ANNOTATION_CLASSS_NAME)) + final ConversionServiceAdapterDescriptor descriptor) { + return AnnotationSpec.builder(QUALIFIER_ANNOTATION_CLASS_NAME) .addMember("value", "$S", descriptor.getConversionServiceBeanName()) .build(); } private static AnnotationSpec buildLazyAnnotation() { - return AnnotationSpec.builder( - ClassName.get(LAZY_ANNOTATION_PACKAGE_NAME, LAZY_ANNOTATION_CLASS_NAME)) - .build(); + return AnnotationSpec.builder(LAZY_ANNOTATION_CLASS_NAME).build(); + } + + private String collectionOfMethodName(final ParameterizedTypeName parameterizedTypeName) { + if (isCollectionWithGenericParameter(parameterizedTypeName)) { + return simpleName(parameterizedTypeName) + + "Of" + + collectionOfNameIfApplicable(parameterizedTypeName.typeArguments.iterator().next()); + } + + return simpleName(parameterizedTypeName); + } + + private boolean isCollectionWithGenericParameter(final ParameterizedTypeName parameterizedTypeName) { + return parameterizedTypeName.typeArguments != null + && parameterizedTypeName.typeArguments.size() > 0 + && isCollection(parameterizedTypeName); + } + + private boolean isCollection(final ParameterizedTypeName parameterizedTypeName) { + try { + return Collection.class.isAssignableFrom( + Class.forName(parameterizedTypeName.rawType.canonicalName())); + } catch (ClassNotFoundException e) { + processingEnvironment + .get() + .getMessager() + .printMessage( + WARNING, + "Caught ClassNotFoundException when trying to resolve parameterized type: " + + e.getMessage()); + return false; + } + } + + private String collectionOfNameIfApplicable(final TypeName typeName) { + if (typeName instanceof ParameterizedTypeName) { + return collectionOfMethodName((ParameterizedTypeName) typeName); + } + return simpleName(typeName); } private static String simpleName(final TypeName typeName) { @@ -110,7 +179,7 @@ private static String simpleName(final TypeName typeName) { } else return String.valueOf(typeName); } - private static String arraySimpleName(ArrayTypeName arrayTypeName) { + private static String arraySimpleName(final ArrayTypeName arrayTypeName) { return "ArrayOf" + (arrayTypeName.componentType instanceof ArrayTypeName ? arraySimpleName((ArrayTypeName) arrayTypeName.componentType) @@ -124,9 +193,9 @@ private static TypeName rawType(final TypeName typeName) { return typeName; } - private static Iterable buildMappingMethods( - final ConversionServiceAdapterDescriptor descriptor, - final FieldSpec injectedConversionServiceFieldSpec) { + private Iterable buildMappingMethods( + final ConversionServiceAdapterDescriptor descriptor, + final FieldSpec injectedConversionServiceFieldSpec) { return descriptor.getFromToMappings().stream() .map( sourceTargetPair -> @@ -134,50 +203,74 @@ private static Iterable buildMappingMethods( .collect(toList()); } - private static MethodSpec toMappingMethodSpec( - final FieldSpec injectedConversionServiceFieldSpec, - final Pair sourceTargetPair) { + private MethodSpec toMappingMethodSpec( + final FieldSpec injectedConversionServiceFieldSpec, + final Pair sourceTargetPair) { final ParameterSpec sourceParameterSpec = buildSourceParameterSpec(sourceTargetPair.getLeft()); return MethodSpec.methodBuilder( String.format( "map%sTo%s", - simpleName(sourceTargetPair.getLeft()), simpleName(sourceTargetPair.getRight()))) + collectionOfNameIfApplicable(sourceTargetPair.getLeft()), + collectionOfNameIfApplicable(sourceTargetPair.getRight()))) .addParameter(sourceParameterSpec) .addModifiers(PUBLIC) .returns(sourceTargetPair.getRight()) .addStatement( - "return $N.convert($N, $T.class)", - injectedConversionServiceFieldSpec, - sourceParameterSpec, - rawType(sourceTargetPair.getRight())) + String.format( + "return ($T) $N.convert($N, %s, %s)", + typeDescriptorFormat(sourceTargetPair.getLeft()), + typeDescriptorFormat(sourceTargetPair.getRight())), + allTypeDescriptorArguments(injectedConversionServiceFieldSpec, sourceParameterSpec, sourceTargetPair)) .build(); } + private Object[] allTypeDescriptorArguments( + final FieldSpec injectedConversionServiceFieldSpec, + final ParameterSpec sourceParameterSpec, + final Pair sourceTargetPair) { + return concat( + concat( + Stream.of( + sourceTargetPair.getRight(), + injectedConversionServiceFieldSpec, + sourceParameterSpec), + typeDescriptorArguments(sourceTargetPair.getLeft())), + typeDescriptorArguments(sourceTargetPair.getRight())) + .toArray(); + } + + private String typeDescriptorFormat(final TypeName typeName) { + if (typeName instanceof ParameterizedTypeName + && isCollectionWithGenericParameter((ParameterizedTypeName) typeName)) { + return String.format( + "$T.collection($T.class, %s)", + typeDescriptorFormat(((ParameterizedTypeName) typeName).typeArguments.iterator().next())); + } + return "$T.valueOf($T.class)"; + } + + private Stream typeDescriptorArguments(final TypeName typeName) { + return typeName instanceof ParameterizedTypeName + && isCollectionWithGenericParameter((ParameterizedTypeName) typeName) + ? concat( + Stream.of(TYPE_DESCRIPTOR_CLASS_NAME, ((ParameterizedTypeName) typeName).rawType), + typeDescriptorArguments( + ((ParameterizedTypeName) typeName).typeArguments.iterator().next())) + : Stream.of(TYPE_DESCRIPTOR_CLASS_NAME, rawType(typeName)); + } + private static ParameterSpec buildSourceParameterSpec(final TypeName sourceClassName) { return ParameterSpec.builder(sourceClassName, "source", FINAL).build(); } private static FieldSpec buildConversionServiceFieldSpec() { return FieldSpec.builder( - ClassName.get(CONVERSION_SERVICE_PACKAGE_NAME, CONVERSION_SERVICE_CLASS_NAME), - CONVERSION_SERVICE_FIELD_NAME, - PRIVATE, - FINAL) + CONVERSION_SERVICE_CLASS_NAME, CONVERSION_SERVICE_FIELD_NAME, PRIVATE, FINAL) .build(); } - private AnnotationSpec buildGeneratedAnnotationSpec( - ConversionServiceAdapterDescriptor descriptor) { - final AnnotationSpec.Builder builder; - if (descriptor.isSourceVersionAtLeast9() - && isTypeAvailable(descriptor, "javax.annotation.processing.Generated")) { - builder = AnnotationSpec.builder(ClassName.get("javax.annotation.processing", "Generated")); - } else if (isTypeAvailable(descriptor, "javax.annotation.Generated")) { - builder = AnnotationSpec.builder(ClassName.get("javax.annotation", "Generated")); - } else { - builder = null; - } - return Optional.ofNullable(builder) + private AnnotationSpec buildGeneratedAnnotationSpec() { + return Optional.ofNullable(baseAnnotationSpecBuilder()) .map( build -> build.addMember("value", "$S", ConversionServiceAdapterGenerator.class.getName())) @@ -186,8 +279,38 @@ && isTypeAvailable(descriptor, "javax.annotation.processing.Generated")) { .orElse(null); } - private static boolean isTypeAvailable( - final ConversionServiceAdapterDescriptor descriptor, final String name) { - return descriptor.getElementUtils().getTypeElement(name) != null; + private AnnotationSpec.Builder baseAnnotationSpecBuilder() { + final AnnotationSpec.Builder builder; + if (isJava9PlusGeneratedAvailable()) { + builder = AnnotationSpec.builder(JAVA_9_PLUS_ANNOTATION_GENERATED_CLASS_NAME); + } else if (isPreJava9GeneratedAvailable()) { + builder = AnnotationSpec.builder(PRE_JAVA_9_ANNOTATION_GENERATED_CLASS_NAME); + } else { + builder = null; + } + return builder; + } + + private boolean isPreJava9GeneratedAvailable() { + return isTypeAvailable(PRE_JAVA_9_ANNOTATION_GENERATED); + } + + private boolean isJava9PlusGeneratedAvailable() { + return isSourceVersionAtLeast9() + && isTypeAvailable(JAVA_9_PLUS_ANNOTATION_GENERATED); + } + + private boolean isSourceVersionAtLeast9() { + return processingEnvironment.get().getSourceVersion().compareTo(RELEASE_8) > 0; + } + + private boolean isTypeAvailable(final String name) { + return processingEnvironment.get().getElementUtils().getTypeElement(name) != null; + } + + public void init(final ProcessingEnvironment processingEnv) { + if (!this.processingEnvironment.compareAndSet(null, processingEnv)) { + throw new IllegalStateException("ProcessingEnvironment already set."); + } } } diff --git a/extensions/src/main/java/org/mapstruct/extensions/spring/converter/ConverterMapperProcessor.java b/extensions/src/main/java/org/mapstruct/extensions/spring/converter/ConverterMapperProcessor.java index a8f3968..cd5b44a 100644 --- a/extensions/src/main/java/org/mapstruct/extensions/spring/converter/ConverterMapperProcessor.java +++ b/extensions/src/main/java/org/mapstruct/extensions/spring/converter/ConverterMapperProcessor.java @@ -1,32 +1,31 @@ package org.mapstruct.extensions.spring.converter; +import static java.lang.Boolean.TRUE; +import static java.util.Collections.emptyList; +import static java.util.stream.Collectors.toCollection; +import static java.util.stream.Collectors.toList; +import static javax.lang.model.type.TypeKind.DECLARED; +import static javax.tools.Diagnostic.Kind.ERROR; + import com.squareup.javapoet.ClassName; import com.squareup.javapoet.TypeName; -import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.tuple.MutablePair; -import org.apache.commons.lang3.tuple.Pair; -import org.mapstruct.extensions.spring.SpringMapperConfig; - +import java.io.IOException; +import java.io.Writer; +import java.time.Clock; +import java.util.*; +import java.util.Map.Entry; import javax.annotation.processing.AbstractProcessor; +import javax.annotation.processing.ProcessingEnvironment; import javax.annotation.processing.RoundEnvironment; import javax.annotation.processing.SupportedAnnotationTypes; import javax.lang.model.SourceVersion; import javax.lang.model.element.*; import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeMirror; -import java.io.IOException; -import java.io.Writer; -import java.time.Clock; -import java.util.*; -import java.util.Map.Entry; - -import static java.lang.Boolean.TRUE; -import static java.util.Collections.emptyList; -import static java.util.stream.Collectors.toCollection; -import static java.util.stream.Collectors.toList; -import static javax.lang.model.SourceVersion.RELEASE_8; -import static javax.lang.model.type.TypeKind.DECLARED; -import static javax.tools.Diagnostic.Kind.ERROR; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.tuple.MutablePair; +import org.apache.commons.lang3.tuple.Pair; +import org.mapstruct.extensions.spring.SpringMapperConfig; @SupportedAnnotationTypes({ ConverterMapperProcessor.MAPPER, @@ -50,6 +49,12 @@ public ConverterMapperProcessor() { this.adapterGenerator = adapterGenerator; } + @Override + public synchronized void init(final ProcessingEnvironment processingEnv) { + super.init(processingEnv); + adapterGenerator.init(processingEnv); + } + @Override public SourceVersion getSupportedSourceVersion() { return SourceVersion.latestSupported(); @@ -69,29 +74,26 @@ private ConversionServiceAdapterDescriptor buildDescriptor( final Set annotations, final RoundEnvironment roundEnv) { return new ConversionServiceAdapterDescriptor() .adapterClassName(getAdapterClassName(annotations, roundEnv)) - .conversionServiceBeanName(getConversionServiceName(annotations, roundEnv)) + .conversionServiceBeanName(getConversionServiceBeanName(annotations, roundEnv)) .lazyAnnotatedConversionServiceBean( getLazyAnnotatedConversionServiceBean(annotations, roundEnv)) - .fromToMappings(getExternalConversionMappings(annotations, roundEnv)) - .elementUtils(processingEnv.getElementUtils()) - .sourceVersionAtLeast9(processingEnv.getSourceVersion().compareTo(RELEASE_8) > 0); + .fromToMappings(getExternalConversionMappings(annotations, roundEnv)); } private List> getExternalConversionMappings( final Set annotations, final RoundEnvironment roundEnv) { - final Optional>> pairs = - annotations.stream() - .filter(ConverterMapperProcessor::isSpringMapperConfigAnnotation) - .findFirst() - .flatMap(annotation -> findFirstElementAnnotatedWith(roundEnv, annotation)) - .flatMap(this::toSpringMapperConfigMirror) - .map(AnnotationMirror::getElementValues) - .flatMap(this::extractExternalConversions) - .map(Entry::getValue) - .map(AnnotationValue::getValue) - .map(List.class::cast) - .map(this::toSourceTargetTypeNamePairs); - return pairs.orElse(emptyList()); + return annotations.stream() + .filter(ConverterMapperProcessor::isSpringMapperConfigAnnotation) + .findFirst() + .flatMap(annotation -> findFirstElementAnnotatedWith(roundEnv, annotation)) + .flatMap(this::toSpringMapperConfigMirror) + .map(AnnotationMirror::getElementValues) + .flatMap(this::extractExternalConversions) + .map(Entry::getValue) + .map(AnnotationValue::getValue) + .map(List.class::cast) + .map(this::toSourceTargetTypeNamePairs) + .orElse(emptyList()); } private List> toSourceTargetTypeNamePairs( @@ -255,13 +257,13 @@ private String getPackageName(Element element) { return String.valueOf(processingEnv.getElementUtils().getPackageOf(element).getQualifiedName()); } - private String getConversionServiceName( + private static String getConversionServiceBeanName( final Set annotations, final RoundEnvironment roundEnv) { return annotations.stream() .filter(ConverterMapperProcessor::isSpringMapperConfigAnnotation) .findFirst() .flatMap(annotation -> findFirstElementAnnotatedWith(roundEnv, annotation)) - .map(this::toSpringMapperConfig) + .map(ConverterMapperProcessor::toSpringMapperConfig) .map(SpringMapperConfig::conversionServiceBeanName) .orElse(null); } @@ -271,7 +273,7 @@ private static Optional findFirstElementAnnotatedWith( return roundEnv.getElementsAnnotatedWith(annotation).stream().findFirst(); } - private SpringMapperConfig toSpringMapperConfig(final Element element) { + private static SpringMapperConfig toSpringMapperConfig(final Element element) { return element.getAnnotation(SpringMapperConfig.class); } @@ -289,13 +291,13 @@ private boolean isSpringMapperConfigMirror(final AnnotationMirror annotationMirr .equals(annotationMirror.getAnnotationType().asElement().asType()); } - private boolean getLazyAnnotatedConversionServiceBean( + private static boolean getLazyAnnotatedConversionServiceBean( final Set annotations, final RoundEnvironment roundEnv) { return annotations.stream() .filter(ConverterMapperProcessor::isSpringMapperConfigAnnotation) .findFirst() .flatMap(annotation -> findFirstElementAnnotatedWith(roundEnv, annotation)) - .map(this::toSpringMapperConfig) + .map(ConverterMapperProcessor::toSpringMapperConfig) .map(SpringMapperConfig::lazyAnnotatedConversionServiceBean) .orElse(TRUE); } 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 8d9b195..2b56c4f 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,26 @@ package org.mapstruct.extensions.spring.converter; +import static java.nio.charset.StandardCharsets.UTF_8; +import static javax.lang.model.SourceVersion.RELEASE_8; +import static javax.lang.model.SourceVersion.RELEASE_9; +import static org.apache.commons.io.IOUtils.resourceToString; +import static org.assertj.core.api.BDDAssertions.then; +import static org.assertj.core.api.BDDAssertions.thenIllegalStateException; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; + import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.ParameterizedTypeName; +import java.io.IOException; +import java.io.StringWriter; +import java.time.Clock; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.util.List; +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.SourceVersion; +import javax.lang.model.element.TypeElement; +import javax.lang.model.util.Elements; import org.apache.commons.lang3.tuple.Pair; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; @@ -8,21 +28,7 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; - -import javax.lang.model.element.TypeElement; -import javax.lang.model.util.Elements; -import java.io.IOException; -import java.io.StringWriter; -import java.time.Clock; -import java.time.ZoneId; -import java.time.ZonedDateTime; - -import static java.nio.charset.StandardCharsets.UTF_8; -import static java.util.Collections.singletonList; -import static org.apache.commons.io.IOUtils.resourceToString; -import static org.assertj.core.api.BDDAssertions.then; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; +import org.mockito.stubbing.Answer; @ExtendWith(MockitoExtension.class) class ConversionServiceAdapterGeneratorTest { @@ -35,73 +41,92 @@ class ConversionServiceAdapterGeneratorTest { ZonedDateTime.of(2020, 3, 29, 15, 21, 34, (int) (236 * Math.pow(10, 6)), ZoneId.of("Z")) .toInstant(), ZoneId.of("Z")); - private final ConversionServiceAdapterGenerator generator = + private final ConversionServiceAdapterGenerator underTest = new ConversionServiceAdapterGenerator(FIXED_CLOCK); @Nested - class Java8Generated { - @BeforeEach - void initElements() { - isAtLeastJava9 = false; - given(elements.getTypeElement("javax.annotation.Generated")) - .willReturn(mock(TypeElement.class)); - } - - @Test - void shouldGenerateMatchingOutput() throws IOException { - ConversionServiceAdapterGeneratorTest.this.shouldGenerateMatchingOutput( - "ConversionServiceAdapterJava8Generated.java"); - } - - @Test - void shouldGenerateMatchingOutputWhenUsingCustomConversionService() throws IOException { - ConversionServiceAdapterGeneratorTest.this - .shouldGenerateMatchingOutputWhenUsingCustomConversionService( - "ConversionServiceAdapterCustomBeanJava8Generated.java"); - } - } - - @Nested - class Java9PlusGenerated { + class DefaultProcessingEnvironment { @BeforeEach - void initElements() { - isAtLeastJava9 = true; - given(elements.getTypeElement("javax.annotation.processing.Generated")) - .willReturn(mock(TypeElement.class)); + 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; + } + }); + underTest.init(processingEnvironment); } - - @Test - void shouldGenerateMatchingOutput() throws IOException { - ConversionServiceAdapterGeneratorTest.this.shouldGenerateMatchingOutput( - "ConversionServiceAdapterJava9PlusGenerated.java"); + + @Nested + class Java8Generated { + @BeforeEach + void initElements() { + isAtLeastJava9 = false; + given(elements.getTypeElement("javax.annotation.Generated")) + .willReturn(mock(TypeElement.class)); + } + + @Test + void shouldGenerateMatchingOutput() throws IOException { + ConversionServiceAdapterGeneratorTest.this.shouldGenerateMatchingOutput( + "ConversionServiceAdapterJava8Generated.java"); + } + + @Test + void shouldGenerateMatchingOutputWhenUsingCustomConversionService() throws IOException { + ConversionServiceAdapterGeneratorTest.this + .shouldGenerateMatchingOutputWhenUsingCustomConversionService( + "ConversionServiceAdapterCustomBeanJava8Generated.java"); + } } - @Test - void shouldGenerateMatchingOutputWhenUsingCustomConversionService() throws IOException { - ConversionServiceAdapterGeneratorTest.this - .shouldGenerateMatchingOutputWhenUsingCustomConversionService( - "ConversionServiceAdapterCustomBeanJava9PlusGenerated.java"); + @Nested + class Java9PlusGenerated { + @BeforeEach + void initElements() { + isAtLeastJava9 = true; + given(elements.getTypeElement("javax.annotation.processing.Generated")) + .willReturn(mock(TypeElement.class)); + } + + @Test + void shouldGenerateMatchingOutput() throws IOException { + ConversionServiceAdapterGeneratorTest.this.shouldGenerateMatchingOutput( + "ConversionServiceAdapterJava9PlusGenerated.java"); + } + + @Test + void shouldGenerateMatchingOutputWhenUsingCustomConversionService() throws IOException { + ConversionServiceAdapterGeneratorTest.this + .shouldGenerateMatchingOutputWhenUsingCustomConversionService( + "ConversionServiceAdapterCustomBeanJava9PlusGenerated.java"); + } } - } - @Nested - class NoGenerated { - @BeforeEach - void initElements() { - isAtLeastJava9 = false; - } - - @Test - void shouldGenerateMatchingOutput() throws IOException { - ConversionServiceAdapterGeneratorTest.this.shouldGenerateMatchingOutput( - "ConversionServiceAdapterNoGenerated.java"); - } - - @Test - void shouldGenerateMatchingOutputWhenUsingCustomConversionService() throws IOException { - ConversionServiceAdapterGeneratorTest.this - .shouldGenerateMatchingOutputWhenUsingCustomConversionService( - "ConversionServiceAdapterCustomBeanNoGenerated.java"); + @Nested + class NoGenerated { + @BeforeEach + void initElements() { + isAtLeastJava9 = false; + } + + @Test + void shouldGenerateMatchingOutput() throws IOException { + ConversionServiceAdapterGeneratorTest.this.shouldGenerateMatchingOutput( + "ConversionServiceAdapterNoGenerated.java"); + } + + @Test + void shouldGenerateMatchingOutputWhenUsingCustomConversionService() throws IOException { + ConversionServiceAdapterGeneratorTest.this + .shouldGenerateMatchingOutputWhenUsingCustomConversionService( + "ConversionServiceAdapterCustomBeanNoGenerated.java"); + } } } @@ -114,15 +139,18 @@ void shouldGenerateMatchingOutput(final String expectedContentFileName) throws I ConversionServiceAdapterGeneratorTest.class.getPackage().getName(), "ConversionServiceAdapter")) .fromToMappings( - singletonList( - Pair.of(ClassName.get("test", "Car"), ClassName.get("test", "CarDto")))) - .lazyAnnotatedConversionServiceBean(true) - .elementUtils(elements) - .sourceVersionAtLeast9(isAtLeastJava9); + 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 - generator.writeConversionServiceAdapter(descriptor, outputWriter); + underTest.writeConversionServiceAdapter(descriptor, outputWriter); // Then then(outputWriter.toString()) @@ -140,18 +168,38 @@ void shouldGenerateMatchingOutputWhenUsingCustomConversionService( "ConversionServiceAdapter")) .conversionServiceBeanName("myConversionService") .fromToMappings( - singletonList( - Pair.of(ClassName.get("test", "Car"), ClassName.get("test", "CarDto")))) - .lazyAnnotatedConversionServiceBean(true) - .elementUtils(elements) - .sourceVersionAtLeast9(isAtLeastJava9); + 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 - generator.writeConversionServiceAdapter(descriptor, outputWriter); + underTest.writeConversionServiceAdapter(descriptor, outputWriter); // Then then(outputWriter.toString()) .isEqualToIgnoringWhitespace(resourceToString('/' + expectedContentFileName, UTF_8)); } + + @Nested + class Init { + @Test + void shouldInitWithProcessingEnvironment() { + final var processingEnv = mock(ProcessingEnvironment.class); + underTest.init(processingEnv); + then(underTest.getProcessingEnvironment()).isEqualTo(processingEnv); + } + + @Test + void shouldThrowIllegalStateExceptionWhenCalledRepeatedly() { + final var processingEnv = mock(ProcessingEnvironment.class); + underTest.init(processingEnv); + thenIllegalStateException().isThrownBy(() -> underTest.init(processingEnv)); + } + } } diff --git a/extensions/src/test/resources/ConversionServiceAdapterCustomBeanJava8Generated.java b/extensions/src/test/resources/ConversionServiceAdapterCustomBeanJava8Generated.java index a0168fc..07eed4e 100644 --- a/extensions/src/test/resources/ConversionServiceAdapterCustomBeanJava8Generated.java +++ b/extensions/src/test/resources/ConversionServiceAdapterCustomBeanJava8Generated.java @@ -1,9 +1,11 @@ package org.mapstruct.extensions.spring.converter; +import java.util.List; import javax.annotation.Generated; import org.springframework.beans.factory.annotation.Qualifier; 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; @@ -21,6 +23,10 @@ public ConversionServiceAdapter( } public CarDto mapCarToCarDto(final Car source) { - return conversionService.convert(source, CarDto.class); + 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/ConversionServiceAdapterCustomBeanJava9PlusGenerated.java b/extensions/src/test/resources/ConversionServiceAdapterCustomBeanJava9PlusGenerated.java index 54a9eea..f57685b 100644 --- a/extensions/src/test/resources/ConversionServiceAdapterCustomBeanJava9PlusGenerated.java +++ b/extensions/src/test/resources/ConversionServiceAdapterCustomBeanJava9PlusGenerated.java @@ -1,9 +1,11 @@ package org.mapstruct.extensions.spring.converter; +import java.util.List; import javax.annotation.processing.Generated; import org.springframework.beans.factory.annotation.Qualifier; 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; @@ -21,6 +23,10 @@ public ConversionServiceAdapter( } public CarDto mapCarToCarDto(final Car source) { - return conversionService.convert(source, CarDto.class); + 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/ConversionServiceAdapterCustomBeanNoGenerated.java b/extensions/src/test/resources/ConversionServiceAdapterCustomBeanNoGenerated.java index 452ffee..4dbdc84 100644 --- a/extensions/src/test/resources/ConversionServiceAdapterCustomBeanNoGenerated.java +++ b/extensions/src/test/resources/ConversionServiceAdapterCustomBeanNoGenerated.java @@ -1,8 +1,10 @@ package org.mapstruct.extensions.spring.converter; +import java.util.List; import org.springframework.beans.factory.annotation.Qualifier; 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; @@ -17,6 +19,10 @@ public ConversionServiceAdapter( } public CarDto mapCarToCarDto(final Car source) { - return conversionService.convert(source, CarDto.class); + 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/ConversionServiceAdapterJava8Generated.java b/extensions/src/test/resources/ConversionServiceAdapterJava8Generated.java index d2c027a..664a3b6 100644 --- a/extensions/src/test/resources/ConversionServiceAdapterJava8Generated.java +++ b/extensions/src/test/resources/ConversionServiceAdapterJava8Generated.java @@ -1,8 +1,10 @@ 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; @@ -19,6 +21,10 @@ public ConversionServiceAdapter(@Lazy final ConversionService conversionService) } public CarDto mapCarToCarDto(final Car source) { - return conversionService.convert(source, CarDto.class); + 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/ConversionServiceAdapterJava9PlusGenerated.java b/extensions/src/test/resources/ConversionServiceAdapterJava9PlusGenerated.java index 6972a38..eec5416 100644 --- a/extensions/src/test/resources/ConversionServiceAdapterJava9PlusGenerated.java +++ b/extensions/src/test/resources/ConversionServiceAdapterJava9PlusGenerated.java @@ -1,8 +1,10 @@ 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; @@ -19,6 +21,10 @@ public ConversionServiceAdapter(@Lazy final ConversionService conversionService) } public CarDto mapCarToCarDto(final Car source) { - return conversionService.convert(source, CarDto.class); + 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/ConversionServiceAdapterNoGenerated.java b/extensions/src/test/resources/ConversionServiceAdapterNoGenerated.java index 8f99a9f..526eb92 100644 --- a/extensions/src/test/resources/ConversionServiceAdapterNoGenerated.java +++ b/extensions/src/test/resources/ConversionServiceAdapterNoGenerated.java @@ -1,7 +1,9 @@ package org.mapstruct.extensions.spring.converter; +import java.util.List; 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; @@ -15,6 +17,10 @@ public ConversionServiceAdapter(@Lazy final ConversionService conversionService) } public CarDto mapCarToCarDto(final Car source) { - return conversionService.convert(source, CarDto.class); + 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/gradle/libs.versions.toml b/gradle/libs.versions.toml index 6dbcf73..08ba3e2 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -3,17 +3,17 @@ asciidoctorPlugin = "3.3.2" assertj = "3.24.2" commons-io = "2.11.0" commons-lang3 = "3.12.0" -dependencyCheck = "8.0.2" +dependencyCheck = "8.2.1" guava = "31.1-jre" javapoet = "1.13.0" jsr250 = "1.0" jupiter = "5.9.2" mapstruct = "1.4.0.Final" -mockito = "5.1.1" +mockito = "5.3.1" spring = "5.0.0.RELEASE" springBoot = "2.7.7" springDependencyManagement = "1.1.0" -versionsPlugin = "0.45.0" +versionsPlugin = "0.46.0" [libraries] assertj = { group = "org.assertj", name = "assertj-core", version.ref = "assertj"} diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 943f0cb..c1962a7 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index f398c33..37aef8d 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip networkTimeout=10000 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 65dcd68..aeb74cb 100755 --- a/gradlew +++ b/gradlew @@ -85,9 +85,6 @@ done APP_BASE_NAME=${0##*/} APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' - # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -144,7 +141,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac @@ -152,7 +149,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then '' | soft) :;; #( *) # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -197,6 +194,10 @@ if "$cygwin" || "$msys" ; then done fi + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + # Collect all arguments for the java command; # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of # shell script including quotes and variable substitutions, so put them in