From cac4d98284dfae8bfedc1965acfc7ce2afbc0d65 Mon Sep 17 00:00:00 2001 From: "denis.simonov" Date: Tue, 28 Jan 2025 17:46:38 +0200 Subject: [PATCH 1/5] 119 Improvement: Customize class name and bean name for ConverterRegistrationConfiguration Added a way to define custom class name for ConverterRegistrationConfiguration --- .../extensions/spring/SpringMapperConfig.java | 18 ++- .../build.gradle | 14 +++ .../customconfiguration/CarMapper.java | 13 ++ .../MapperSpringConfig.java | 12 ++ .../SeatConfigurationMapper.java | 14 +++ .../customconfiguration/WheelMapper.java | 12 ++ .../WheelsDtoListMapper.java | 13 ++ .../customconfiguration/WheelsMapper.java | 14 +++ ...nversionServiceAdapterIntegrationTest.java | 113 ++++++++++++++++++ .../ConversionServiceAdapterDescriptor.java | 22 +++- .../ConversionServiceAdapterGenerator.java | 11 +- .../converter/ConverterMapperProcessor.java | 20 +++- .../converter/ConverterScanGenerator.java | 10 +- 13 files changed, 264 insertions(+), 22 deletions(-) create mode 100644 examples/custom-converter-registration-configuration-with-converter-scan/build.gradle create mode 100644 examples/custom-converter-registration-configuration-with-converter-scan/src/main/java/org/mapstruct/extensions/spring/example/customconfiguration/CarMapper.java create mode 100644 examples/custom-converter-registration-configuration-with-converter-scan/src/main/java/org/mapstruct/extensions/spring/example/customconfiguration/MapperSpringConfig.java create mode 100644 examples/custom-converter-registration-configuration-with-converter-scan/src/main/java/org/mapstruct/extensions/spring/example/customconfiguration/SeatConfigurationMapper.java create mode 100644 examples/custom-converter-registration-configuration-with-converter-scan/src/main/java/org/mapstruct/extensions/spring/example/customconfiguration/WheelMapper.java create mode 100644 examples/custom-converter-registration-configuration-with-converter-scan/src/main/java/org/mapstruct/extensions/spring/example/customconfiguration/WheelsDtoListMapper.java create mode 100644 examples/custom-converter-registration-configuration-with-converter-scan/src/main/java/org/mapstruct/extensions/spring/example/customconfiguration/WheelsMapper.java create mode 100644 examples/custom-converter-registration-configuration-with-converter-scan/src/test/java/org/mapstruct/extensions/spring/example/ConversionServiceAdapterIntegrationTest.java diff --git a/annotations/src/main/java/org/mapstruct/extensions/spring/SpringMapperConfig.java b/annotations/src/main/java/org/mapstruct/extensions/spring/SpringMapperConfig.java index 40107ac..1e5e1a1 100644 --- a/annotations/src/main/java/org/mapstruct/extensions/spring/SpringMapperConfig.java +++ b/annotations/src/main/java/org/mapstruct/extensions/spring/SpringMapperConfig.java @@ -14,6 +14,20 @@ @Target(ElementType.TYPE) @Retention(RetentionPolicy.SOURCE) public @interface SpringMapperConfig { + + String DEFAULT_CONVERSION_SERVICE_BEAN_NAME = "conversionService"; + String DEFAULT_ADAPTER_CLASS_NAME = "ConversionServiceAdapter"; + String DEFAULT_CONFIGURATION_CLASS_NAME = "ConverterRegistrationConfiguration"; + + /** + * The class name for the generated Configuration class, + * which is performing auto-registration of converters/mappers + * to Spring's {@link org.springframework.core.convert.ConversionService}. + * + * @return The class name for the generated Configuration. + */ + String converterRegistrationConfigurationClassName() default DEFAULT_CONFIGURATION_CLASS_NAME; + /** * The package name for the generated Adapter between the MapStruct mappers and Spring's {@link * org.springframework.core.convert.ConversionService}. If omitted or empty, the package name will @@ -29,7 +43,7 @@ * * @return The class name for the generated Adapter. */ - String conversionServiceAdapterClassName() default "ConversionServiceAdapter"; + String conversionServiceAdapterClassName() default DEFAULT_ADAPTER_CLASS_NAME; /** * The bean name for the Spring {@link org.springframework.core.convert.ConversionService} to use. @@ -37,7 +51,7 @@ * @return The bean name for the Spring {@link * org.springframework.core.convert.ConversionService}. */ - String conversionServiceBeanName() default ""; + String conversionServiceBeanName() default DEFAULT_CONVERSION_SERVICE_BEAN_NAME; /** * To set if the Lazy annotation will be added to the ConversionService's usage in the diff --git a/examples/custom-converter-registration-configuration-with-converter-scan/build.gradle b/examples/custom-converter-registration-configuration-with-converter-scan/build.gradle new file mode 100644 index 0000000..53734ad --- /dev/null +++ b/examples/custom-converter-registration-configuration-with-converter-scan/build.gradle @@ -0,0 +1,14 @@ +dependencies { + annotationProcessor project(":extensions") + implementation projects.examples.model + implementation projects.annotations + + testImplementation libs.assertj + testImplementation libs.bundles.junit.jupiter + implementation libs.jsr250 + implementation libs.mapstruct.core + annotationProcessor libs.mapstruct.processor + implementation libs.spring.context + implementation libs.spring.core + testImplementation libs.spring.test +} diff --git a/examples/custom-converter-registration-configuration-with-converter-scan/src/main/java/org/mapstruct/extensions/spring/example/customconfiguration/CarMapper.java b/examples/custom-converter-registration-configuration-with-converter-scan/src/main/java/org/mapstruct/extensions/spring/example/customconfiguration/CarMapper.java new file mode 100644 index 0000000..66224ec --- /dev/null +++ b/examples/custom-converter-registration-configuration-with-converter-scan/src/main/java/org/mapstruct/extensions/spring/example/customconfiguration/CarMapper.java @@ -0,0 +1,13 @@ +package org.mapstruct.extensions.spring.example.customconfiguration; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.springframework.core.convert.converter.Converter; +import org.mapstruct.extensions.spring.example.Car; +import org.mapstruct.extensions.spring.example.CarDto; + +@Mapper(config = MapperSpringConfig.class) +public interface CarMapper extends Converter { + @Mapping(target = "seats", source = "seatConfiguration") + CarDto convert(Car car); +} diff --git a/examples/custom-converter-registration-configuration-with-converter-scan/src/main/java/org/mapstruct/extensions/spring/example/customconfiguration/MapperSpringConfig.java b/examples/custom-converter-registration-configuration-with-converter-scan/src/main/java/org/mapstruct/extensions/spring/example/customconfiguration/MapperSpringConfig.java new file mode 100644 index 0000000..ce8e659 --- /dev/null +++ b/examples/custom-converter-registration-configuration-with-converter-scan/src/main/java/org/mapstruct/extensions/spring/example/customconfiguration/MapperSpringConfig.java @@ -0,0 +1,12 @@ +package org.mapstruct.extensions.spring.example.customconfiguration; + +import org.mapstruct.MapperConfig; +import org.mapstruct.extensions.spring.SpringMapperConfig; + +@MapperConfig(componentModel = "spring", uses = ConversionServiceAdapter.class) +@SpringMapperConfig( + converterRegistrationConfigurationClassName = "MyConfiguration", +// conversionServiceBeanName = "conversionService", + generateConverterScan = true) +public interface MapperSpringConfig { +} diff --git a/examples/custom-converter-registration-configuration-with-converter-scan/src/main/java/org/mapstruct/extensions/spring/example/customconfiguration/SeatConfigurationMapper.java b/examples/custom-converter-registration-configuration-with-converter-scan/src/main/java/org/mapstruct/extensions/spring/example/customconfiguration/SeatConfigurationMapper.java new file mode 100644 index 0000000..2ac6369 --- /dev/null +++ b/examples/custom-converter-registration-configuration-with-converter-scan/src/main/java/org/mapstruct/extensions/spring/example/customconfiguration/SeatConfigurationMapper.java @@ -0,0 +1,14 @@ +package org.mapstruct.extensions.spring.example.customconfiguration; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.extensions.spring.example.SeatConfiguration; +import org.mapstruct.extensions.spring.example.SeatConfigurationDto; +import org.springframework.core.convert.converter.Converter; + +@Mapper(config = MapperSpringConfig.class) +public interface SeatConfigurationMapper extends Converter { + @Mapping(target = "seatCount", source = "numberOfSeats") + @Mapping(target = "material", source = "seatMaterial") + SeatConfigurationDto convert(SeatConfiguration seatConfiguration); +} diff --git a/examples/custom-converter-registration-configuration-with-converter-scan/src/main/java/org/mapstruct/extensions/spring/example/customconfiguration/WheelMapper.java b/examples/custom-converter-registration-configuration-with-converter-scan/src/main/java/org/mapstruct/extensions/spring/example/customconfiguration/WheelMapper.java new file mode 100644 index 0000000..1da1c31 --- /dev/null +++ b/examples/custom-converter-registration-configuration-with-converter-scan/src/main/java/org/mapstruct/extensions/spring/example/customconfiguration/WheelMapper.java @@ -0,0 +1,12 @@ +package org.mapstruct.extensions.spring.example.customconfiguration; + +import org.mapstruct.Mapper; +import org.mapstruct.extensions.spring.example.Wheel; +import org.mapstruct.extensions.spring.example.WheelDto; +import org.springframework.core.convert.converter.Converter; + +@Mapper(config = MapperSpringConfig.class) +public interface WheelMapper extends Converter { + @Override + WheelDto convert(Wheel source); +} diff --git a/examples/custom-converter-registration-configuration-with-converter-scan/src/main/java/org/mapstruct/extensions/spring/example/customconfiguration/WheelsDtoListMapper.java b/examples/custom-converter-registration-configuration-with-converter-scan/src/main/java/org/mapstruct/extensions/spring/example/customconfiguration/WheelsDtoListMapper.java new file mode 100644 index 0000000..ded1c93 --- /dev/null +++ b/examples/custom-converter-registration-configuration-with-converter-scan/src/main/java/org/mapstruct/extensions/spring/example/customconfiguration/WheelsDtoListMapper.java @@ -0,0 +1,13 @@ +package org.mapstruct.extensions.spring.example.customconfiguration; + +import java.util.List; +import org.mapstruct.Mapper; +import org.mapstruct.extensions.spring.example.WheelDto; +import org.mapstruct.extensions.spring.example.Wheels; +import org.springframework.core.convert.converter.Converter; + +@Mapper(config = MapperSpringConfig.class) +public interface WheelsDtoListMapper extends Converter, Wheels> { + @Override + Wheels convert(List source); +} diff --git a/examples/custom-converter-registration-configuration-with-converter-scan/src/main/java/org/mapstruct/extensions/spring/example/customconfiguration/WheelsMapper.java b/examples/custom-converter-registration-configuration-with-converter-scan/src/main/java/org/mapstruct/extensions/spring/example/customconfiguration/WheelsMapper.java new file mode 100644 index 0000000..74fb92b --- /dev/null +++ b/examples/custom-converter-registration-configuration-with-converter-scan/src/main/java/org/mapstruct/extensions/spring/example/customconfiguration/WheelsMapper.java @@ -0,0 +1,14 @@ +package org.mapstruct.extensions.spring.example.customconfiguration; + +import java.util.List; +import org.mapstruct.Mapper; +import org.mapstruct.extensions.spring.example.Wheel; +import org.mapstruct.extensions.spring.example.WheelDto; +import org.mapstruct.extensions.spring.example.Wheels; +import org.springframework.core.convert.converter.Converter; + +@Mapper(config = MapperSpringConfig.class, imports = Wheel.class) +public interface WheelsMapper extends Converter> { + @Override + List convert(Wheels source); +} diff --git a/examples/custom-converter-registration-configuration-with-converter-scan/src/test/java/org/mapstruct/extensions/spring/example/ConversionServiceAdapterIntegrationTest.java b/examples/custom-converter-registration-configuration-with-converter-scan/src/test/java/org/mapstruct/extensions/spring/example/ConversionServiceAdapterIntegrationTest.java new file mode 100644 index 0000000..2819fcc --- /dev/null +++ b/examples/custom-converter-registration-configuration-with-converter-scan/src/test/java/org/mapstruct/extensions/spring/example/ConversionServiceAdapterIntegrationTest.java @@ -0,0 +1,113 @@ +package org.mapstruct.extensions.spring.example; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mapstruct.extensions.spring.example.customconfiguration.ConversionServiceAdapter; +import org.mapstruct.extensions.spring.example.customconfiguration.ConverterScan; +import org.mapstruct.extensions.spring.example.customconfiguration.MapperSpringConfig; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.convert.ConversionService; +import org.springframework.core.convert.TypeDescriptor; +import org.springframework.core.convert.support.DefaultConversionService; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import java.util.ArrayList; +import java.util.List; + +import static org.assertj.core.api.BDDAssertions.then; +import static org.mapstruct.extensions.spring.example.CarType.OTHER; +import static org.mapstruct.extensions.spring.example.SeatMaterial.LEATHER; +import static org.mapstruct.extensions.spring.example.WheelPosition.RIGHT_FRONT; + +@ExtendWith(SpringExtension.class) +public class ConversionServiceAdapterIntegrationTest { + private static final String TEST_MAKE = "Volvo"; + private static final CarType TEST_CAR_TYPE = OTHER; + private static final int TEST_NUMBER_OF_SEATS = 5; + private static final SeatMaterial TEST_SEAT_MATERIAL = LEATHER; + private static final int TEST_DIAMETER = 20; + private static final WheelPosition TEST_WHEEL_POSITION = RIGHT_FRONT; + + @Autowired + private ConversionService conversionService; + + @Configuration + @ComponentScan("org.mapstruct.extensions.spring.example.customconfiguration") + static class ScanConfiguration { + @Bean + public ConversionService conversionService() { + return new DefaultConversionService(); + } + } + + @Test + void shouldKnowAllMappers() { + then(conversionService.canConvert(Car.class, CarDto.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 + void shouldMapAllAttributes() { + // Given + final Car car = new Car(); + car.setMake(TEST_MAKE); + car.setType(TEST_CAR_TYPE); + final SeatConfiguration seatConfiguration = new SeatConfiguration(); + seatConfiguration.setSeatMaterial(TEST_SEAT_MATERIAL); + seatConfiguration.setNumberOfSeats(TEST_NUMBER_OF_SEATS); + car.setSeatConfiguration(seatConfiguration); + final Wheels wheels = new Wheels(); + final ArrayList wheelsList = new ArrayList<>(); + final Wheel wheel = new Wheel(); + wheel.setDiameter(TEST_DIAMETER); + wheel.setPosition(TEST_WHEEL_POSITION); + wheelsList.add(wheel); + wheels.setWheelsList(wheelsList); + car.setWheels(wheels); + + // When + final CarDto mappedCar = conversionService.convert(car, CarDto.class); + + // Then + then(mappedCar).isNotNull(); + then(mappedCar.getMake()).isEqualTo(TEST_MAKE); + then(mappedCar.getType()).isEqualTo(String.valueOf(TEST_CAR_TYPE)); + final SeatConfigurationDto mappedCarSeats = mappedCar.getSeats(); + then(mappedCarSeats).isNotNull(); + then(mappedCarSeats.getSeatCount()).isEqualTo(TEST_NUMBER_OF_SEATS); + then(mappedCarSeats.getMaterial()).isEqualTo(String.valueOf(TEST_SEAT_MATERIAL)); + final WheelDto expectedWheelDto = new WheelDto(); + expectedWheelDto.setPosition(String.valueOf(TEST_WHEEL_POSITION)); + expectedWheelDto.setDiameter(TEST_DIAMETER); + then(mappedCar.getWheels()).hasSize(1).containsExactly(expectedWheelDto); + } + + @Test + void shouldMapGenericSourceType() { + // Given + final WheelDto dto = new WheelDto(); + dto.setPosition(String.valueOf(TEST_WHEEL_POSITION)); + dto.setDiameter(TEST_DIAMETER); + final List dtoList = new ArrayList<>(); + dtoList.add(dto); + + // When + final Wheels convertedWheels = conversionService.convert(dtoList, Wheels.class); + + // Then + final Wheel expectedWheel = new Wheel(); + expectedWheel.setPosition(TEST_WHEEL_POSITION); + expectedWheel.setDiameter(TEST_DIAMETER); + then(convertedWheels).isNotNull().hasSize(1).containsExactly(expectedWheel); + } +} 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 f304eed..5a8506f 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 @@ -1,20 +1,27 @@ package org.mapstruct.extensions.spring.converter; import static org.apache.commons.lang3.StringUtils.isNotEmpty; +import static org.mapstruct.extensions.spring.SpringMapperConfig.DEFAULT_CONFIGURATION_CLASS_NAME; +import static org.mapstruct.extensions.spring.SpringMapperConfig.DEFAULT_CONVERSION_SERVICE_BEAN_NAME; import com.squareup.javapoet.ClassName; import java.util.List; public class ConversionServiceAdapterDescriptor { + public static final String DEFAULT_CONVERTER_SCAN_CLASS_NAME = "ConverterScan"; + public static final String DEFAULT_CONVERTER_SCANS_CLASS_NAME = "ConverterScans"; + private ClassName adapterClassName; - private String conversionServiceBeanName; + private String conversionServiceBeanName = DEFAULT_CONVERSION_SERVICE_BEAN_NAME; private List fromToMappings; private boolean lazyAnnotatedConversionServiceBean; + private String configurationClassName = DEFAULT_CONFIGURATION_CLASS_NAME; private boolean generateConverterScan; boolean hasNonDefaultConversionServiceBeanName() { - return isNotEmpty(getConversionServiceBeanName()); + return isNotEmpty(getConversionServiceBeanName()) + && !DEFAULT_CONVERSION_SERVICE_BEAN_NAME.equals(getConversionServiceBeanName()); } public ClassName getAdapterClassName() { @@ -56,6 +63,11 @@ public ConversionServiceAdapterDescriptor lazyAnnotatedConversionServiceBean( return this; } + public ConversionServiceAdapterDescriptor configurationClassName( + final String configurationClassName) { + this.configurationClassName = configurationClassName; + return this; + } public boolean isGenerateConverterScan() { return generateConverterScan; @@ -67,14 +79,14 @@ public ConversionServiceAdapterDescriptor generateConverterScan(final boolean ge } public ClassName getConverterScanClassName() { - return ClassName.get(getAdapterClassName().packageName(), "ConverterScan"); + return ClassName.get(getAdapterClassName().packageName(), DEFAULT_CONVERTER_SCAN_CLASS_NAME); } public ClassName getConverterScansClassName() { - return ClassName.get(getAdapterClassName().packageName(), "ConverterScans"); + return ClassName.get(getAdapterClassName().packageName(), DEFAULT_CONVERTER_SCANS_CLASS_NAME); } public ClassName getConverterRegistrationConfigurationClassName() { - return ClassName.get(getAdapterClassName().packageName(), "ConverterRegistrationConfiguration"); + return ClassName.get(getAdapterClassName().packageName(), configurationClassName); } } 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 0bb16cf..7289f72 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 @@ -11,7 +11,6 @@ import java.util.List; import java.util.Locale; import java.util.Optional; -import java.util.regex.Pattern; import java.util.stream.Stream; public class ConversionServiceAdapterGenerator extends AdapterRelatedGenerator { @@ -26,7 +25,6 @@ public class ConversionServiceAdapterGenerator extends AdapterRelatedGenerator { ClassName.get("org.springframework.core.convert", "TypeDescriptor"); private static final ClassName COMPONENT_ANNOTATION_CLASS_NAME = ClassName.get("org.springframework.stereotype", "Component"); - private static final Pattern NON_WORD_PATTERN = Pattern.compile("\\W"); public ConversionServiceAdapterGenerator(final Clock clock) { super(clock); @@ -60,12 +58,13 @@ private List buildTypeDescriptorFields(ConversionServiceAdapterDescri } private String fieldName(TypeName typeName) { - String fieldName = "TYPE_DESCRIPTOR_" + NON_WORD_PATTERN.matcher(typeName.toString()) - .replaceAll("_") + String fieldName = "TYPE_DESCRIPTOR_" + typeName.toString() + .replace('.', '_') + .replace('<', '_') + .replace('>', '_') + .replace("[]", "_ARRAY") .toUpperCase(Locale.ROOT); - if (fieldName.lastIndexOf('_') == fieldName.length() - 1) { - // drop trailing underscore return fieldName.substring(0, fieldName.length() - 1); } else { return fieldName; 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 c7768c6..c60d844 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 @@ -8,6 +8,8 @@ import static javax.lang.model.type.TypeKind.DECLARED; import static javax.tools.Diagnostic.Kind.ERROR; import static org.apache.commons.lang3.StringUtils.defaultIfBlank; +import static org.mapstruct.extensions.spring.SpringMapperConfig.DEFAULT_CONFIGURATION_CLASS_NAME; +import static org.mapstruct.extensions.spring.SpringMapperConfig.DEFAULT_CONVERSION_SERVICE_BEAN_NAME; import static org.mapstruct.extensions.spring.converter.ModelElementUtils.hasName; import com.squareup.javapoet.ClassName; @@ -134,6 +136,8 @@ private ConversionServiceAdapterDescriptor buildAdapterDescriptor( .generateConverterScan(getGenerateConverterScan(annotations, roundEnv)) .lazyAnnotatedConversionServiceBean( getLazyAnnotatedConversionServiceBean(annotations, roundEnv)) + .configurationClassName( + getConfigurationClassName(annotations, roundEnv)) .fromToMappings(getExternalConversionMappings(annotations, roundEnv)); } @@ -241,8 +245,7 @@ private void processMapperAnnotation( .collect(toList())); adapterDescriptor.fromToMappings(fromToMappings); writeAdapterClassFile(adapterDescriptor); - if (adapterDescriptor.hasNonDefaultConversionServiceBeanName() - && adapterDescriptor.isGenerateConverterScan()) { + if (adapterDescriptor.isGenerateConverterScan()) { writeConverterScanFiles(adapterDescriptor); } } @@ -372,7 +375,7 @@ private static boolean isSpringMapperConfigAnnotation(TypeElement annotation) { private void updateFromDeclaration( final MutablePair adapterPackageAndClass, final Element element) { - final SpringMapperConfig springMapperConfig = element.getAnnotation(SpringMapperConfig.class); + final SpringMapperConfig springMapperConfig = toSpringMapperConfig(element); adapterPackageAndClass.setLeft( Optional.of(springMapperConfig.conversionServiceAdapterPackage()) .filter(StringUtils::isNotBlank) @@ -387,7 +390,7 @@ private String getPackageName(Element element) { private static String getConversionServiceBeanName( final Set annotations, final RoundEnvironment roundEnv) { return getConfigAnnotationAttribute( - annotations, roundEnv, SpringMapperConfig::conversionServiceBeanName, null); + annotations, roundEnv, SpringMapperConfig::conversionServiceBeanName, DEFAULT_CONVERSION_SERVICE_BEAN_NAME); } private static Optional findFirstElementAnnotatedWith( @@ -419,6 +422,15 @@ private static boolean getLazyAnnotatedConversionServiceBean( annotations, roundEnv, SpringMapperConfig::lazyAnnotatedConversionServiceBean, TRUE); } + private static String getConfigurationClassName( + final Set annotations, final RoundEnvironment roundEnv) { + return getConfigAnnotationAttribute( + annotations, + roundEnv, + SpringMapperConfig::converterRegistrationConfigurationClassName, + DEFAULT_CONFIGURATION_CLASS_NAME); + } + private static boolean getGenerateConverterScan( final Set annotations, final RoundEnvironment roundEnv) { return getConfigAnnotationAttribute( diff --git a/extensions/src/main/java/org/mapstruct/extensions/spring/converter/ConverterScanGenerator.java b/extensions/src/main/java/org/mapstruct/extensions/spring/converter/ConverterScanGenerator.java index c690e58..a954d4a 100644 --- a/extensions/src/main/java/org/mapstruct/extensions/spring/converter/ConverterScanGenerator.java +++ b/extensions/src/main/java/org/mapstruct/extensions/spring/converter/ConverterScanGenerator.java @@ -15,10 +15,6 @@ public class ConverterScanGenerator extends AdapterRelatedGenerator { ClassName.get(SPRING_CONTEXT_ANNOTATION_PACKAGE_NAME, "ComponentScan"); private static final ClassName IMPORT_CLASS_NAME = ClassName.get(SPRING_CONTEXT_ANNOTATION_PACKAGE_NAME, "Import"); - private static final AnnotationSpec IMPORT_ANNOTATION_SPEC = - AnnotationSpec.builder(IMPORT_CLASS_NAME) - .addMember("value", "$L", "ConverterRegistrationConfiguration.class") - .build(); private static final AnnotationSpec REPEATABLE_ANNOTATION_SPEC = AnnotationSpec.builder(Repeatable.class) .addMember("value", "$L", "ConverterScans.class") @@ -43,12 +39,16 @@ protected JavaFile.Builder modifyDefaultFileBuilder(final JavaFile.Builder javaF protected TypeSpec createMainTypeSpec(final ConversionServiceAdapterDescriptor descriptor) { final var converterScanClassTypeSpecBuilder = TypeSpec.annotationBuilder(descriptor.getConverterScanClassName()).addModifiers(PUBLIC); + final var importAnnotationSpec = + AnnotationSpec.builder(IMPORT_CLASS_NAME) + .addMember("value", "$L", descriptor.getConverterRegistrationConfigurationClassName() + ".class") + .build(); Optional.ofNullable(buildGeneratedAnnotationSpec()) .ifPresent(converterScanClassTypeSpecBuilder::addAnnotation); converterScanClassTypeSpecBuilder .addAnnotation(COMPONENT_SCAN_CLASS_NAME) .addAnnotation(TARGET_TYPE_ANNOTATION_SPEC) - .addAnnotation(IMPORT_ANNOTATION_SPEC) + .addAnnotation(importAnnotationSpec) .addAnnotation(Documented.class) .addAnnotation(RETENTION_RUNTIME_ANNOTATION_SPEC) .addAnnotation(REPEATABLE_ANNOTATION_SPEC) From a96a5f45a4a29709d2bc1de1eaaf88d1c8d61b2b Mon Sep 17 00:00:00 2001 From: "denis.simonov" Date: Tue, 28 Jan 2025 18:03:02 +0200 Subject: [PATCH 2/5] 119 Improvement: Customize class name and bean name for ConverterRegistrationConfiguration Added a way to define custom class name for ConverterRegistrationConfiguration --- .../spring/example/customconfiguration/MapperSpringConfig.java | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/custom-converter-registration-configuration-with-converter-scan/src/main/java/org/mapstruct/extensions/spring/example/customconfiguration/MapperSpringConfig.java b/examples/custom-converter-registration-configuration-with-converter-scan/src/main/java/org/mapstruct/extensions/spring/example/customconfiguration/MapperSpringConfig.java index ce8e659..82e8c88 100644 --- a/examples/custom-converter-registration-configuration-with-converter-scan/src/main/java/org/mapstruct/extensions/spring/example/customconfiguration/MapperSpringConfig.java +++ b/examples/custom-converter-registration-configuration-with-converter-scan/src/main/java/org/mapstruct/extensions/spring/example/customconfiguration/MapperSpringConfig.java @@ -6,7 +6,6 @@ @MapperConfig(componentModel = "spring", uses = ConversionServiceAdapter.class) @SpringMapperConfig( converterRegistrationConfigurationClassName = "MyConfiguration", -// conversionServiceBeanName = "conversionService", generateConverterScan = true) public interface MapperSpringConfig { } From 66ef19d7dff54d2a53fa2b401db23fbfbf6b28c4 Mon Sep 17 00:00:00 2001 From: "denis.simonov" Date: Tue, 28 Jan 2025 18:11:53 +0200 Subject: [PATCH 3/5] 119 Improvement: Customize class name and bean name for ConverterRegistrationConfiguration Added a way to define custom class name for ConverterRegistrationConfiguration --- .../converter/ConversionServiceAdapterGenerator.java | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) 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 7289f72..0bb16cf 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 @@ -11,6 +11,7 @@ import java.util.List; import java.util.Locale; import java.util.Optional; +import java.util.regex.Pattern; import java.util.stream.Stream; public class ConversionServiceAdapterGenerator extends AdapterRelatedGenerator { @@ -25,6 +26,7 @@ public class ConversionServiceAdapterGenerator extends AdapterRelatedGenerator { ClassName.get("org.springframework.core.convert", "TypeDescriptor"); private static final ClassName COMPONENT_ANNOTATION_CLASS_NAME = ClassName.get("org.springframework.stereotype", "Component"); + private static final Pattern NON_WORD_PATTERN = Pattern.compile("\\W"); public ConversionServiceAdapterGenerator(final Clock clock) { super(clock); @@ -58,13 +60,12 @@ private List buildTypeDescriptorFields(ConversionServiceAdapterDescri } private String fieldName(TypeName typeName) { - String fieldName = "TYPE_DESCRIPTOR_" + typeName.toString() - .replace('.', '_') - .replace('<', '_') - .replace('>', '_') - .replace("[]", "_ARRAY") + String fieldName = "TYPE_DESCRIPTOR_" + NON_WORD_PATTERN.matcher(typeName.toString()) + .replaceAll("_") .toUpperCase(Locale.ROOT); + if (fieldName.lastIndexOf('_') == fieldName.length() - 1) { + // drop trailing underscore return fieldName.substring(0, fieldName.length() - 1); } else { return fieldName; From f82cff984dd313dc199880f8a985493b1cc350e0 Mon Sep 17 00:00:00 2001 From: "denis.simonov" Date: Wed, 29 Jan 2025 10:19:04 +0200 Subject: [PATCH 4/5] 119 Improvement: Customize class name and bean name for ConverterRegistrationConfiguration Added documentation Fixed generated import element to have simple name instead of full name(uncovered by unit test) --- .../chapter-3-mapper-as-converter.asciidoc | 64 +++++++++++++++++++ .../custombean/MapperSpringConfig.java | 2 +- .../ConversionServiceAdapterDescriptor.java | 4 ++ .../converter/ConverterScanGenerator.java | 2 +- 4 files changed, 70 insertions(+), 2 deletions(-) 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 2fe63a0..e998a70 100644 --- a/docs/src/docs/asciidoc/chapter-3-mapper-as-converter.asciidoc +++ b/docs/src/docs/asciidoc/chapter-3-mapper-as-converter.asciidoc @@ -170,6 +170,70 @@ public class ConversionServiceAdapterIntegrationTest { ---- ==== +[[converterRegistrationConfigurationClassName]] +=== Modifying the name for the generated converter registration configuration class + +By default, the converter registration configuration class will have name `ConverterRegistrationConfiguration`. +If you wish to change this, you can do so by setting the property `converterRegistrationConfigurationClassName`: + +==== +[source,java,linenums] +[subs="verbatim,attributes"] +---- +@MapperConfig(componentModel = "spring") +@SpringMapperConfig( + converterRegistrationConfigurationClassName = "MyConfiguration", + generateConverterScan = true) +public interface MapstructConfig {} +---- +==== + +This changes the generated class name to be the property's value: + +==== +[source,java,linenums] +[subs="verbatim,attributes"] +---- +@Configuration +class MyConfiguration { + private final ConfigurableConversionService conversionService; + + private final List> converters; + + MyConfiguration( + @Qualifier("conversionService") final ConfigurableConversionService conversionService, + final List> converters) { + this.conversionService = conversionService; + this.converters = converters; + } + + @PostConstruct + void registerConverters() { + converters.forEach(conversionService::addConverter); + } +} +---- +==== + +Also this changes reference to converter registration configuration class from generated ConverterScan class: + +==== +[source,java,linenums] +[subs="verbatim,attributes"] +---- +@ComponentScan +@Target(TYPE) +@Import(MyConfiguration.class) +@Documented +@Retention(RUNTIME) +@Repeatable(ConverterScans.class) +public @interface ConverterScan { + ... +} +---- +==== + + [[adapterMethodName]] === Modifying the name for the generated adapter method diff --git a/examples/custom-conversion-service-bean/src/main/java/org/mapstruct/extensions/spring/example/custombean/MapperSpringConfig.java b/examples/custom-conversion-service-bean/src/main/java/org/mapstruct/extensions/spring/example/custombean/MapperSpringConfig.java index 43b0138..215a797 100644 --- a/examples/custom-conversion-service-bean/src/main/java/org/mapstruct/extensions/spring/example/custombean/MapperSpringConfig.java +++ b/examples/custom-conversion-service-bean/src/main/java/org/mapstruct/extensions/spring/example/custombean/MapperSpringConfig.java @@ -4,6 +4,6 @@ import org.mapstruct.extensions.spring.SpringMapperConfig; @MapperConfig(componentModel = "spring", uses = ConversionServiceAdapter.class) -@SpringMapperConfig(conversionServiceBeanName = "myConversionService") +@SpringMapperConfig(conversionServiceBeanName = "myConversionService", generateConverterScan = true) public interface MapperSpringConfig { } 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 5a8506f..cc9a978 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 @@ -69,6 +69,10 @@ public ConversionServiceAdapterDescriptor configurationClassName( return this; } + public String getConfigurationClassName() { + return configurationClassName; + } + public boolean isGenerateConverterScan() { return generateConverterScan; } diff --git a/extensions/src/main/java/org/mapstruct/extensions/spring/converter/ConverterScanGenerator.java b/extensions/src/main/java/org/mapstruct/extensions/spring/converter/ConverterScanGenerator.java index a954d4a..f68e57f 100644 --- a/extensions/src/main/java/org/mapstruct/extensions/spring/converter/ConverterScanGenerator.java +++ b/extensions/src/main/java/org/mapstruct/extensions/spring/converter/ConverterScanGenerator.java @@ -41,7 +41,7 @@ protected TypeSpec createMainTypeSpec(final ConversionServiceAdapterDescriptor d TypeSpec.annotationBuilder(descriptor.getConverterScanClassName()).addModifiers(PUBLIC); final var importAnnotationSpec = AnnotationSpec.builder(IMPORT_CLASS_NAME) - .addMember("value", "$L", descriptor.getConverterRegistrationConfigurationClassName() + ".class") + .addMember("value", "$L", descriptor.getConfigurationClassName() + ".class") .build(); Optional.ofNullable(buildGeneratedAnnotationSpec()) .ifPresent(converterScanClassTypeSpecBuilder::addAnnotation); From 41c5d303401b27c92739d67a1be4f82a226d49ec Mon Sep 17 00:00:00 2001 From: "denis.simonov" Date: Wed, 29 Jan 2025 10:22:05 +0200 Subject: [PATCH 5/5] reverted mistakenly committed change --- .../spring/example/custombean/MapperSpringConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/custom-conversion-service-bean/src/main/java/org/mapstruct/extensions/spring/example/custombean/MapperSpringConfig.java b/examples/custom-conversion-service-bean/src/main/java/org/mapstruct/extensions/spring/example/custombean/MapperSpringConfig.java index 215a797..43b0138 100644 --- a/examples/custom-conversion-service-bean/src/main/java/org/mapstruct/extensions/spring/example/custombean/MapperSpringConfig.java +++ b/examples/custom-conversion-service-bean/src/main/java/org/mapstruct/extensions/spring/example/custombean/MapperSpringConfig.java @@ -4,6 +4,6 @@ import org.mapstruct.extensions.spring.SpringMapperConfig; @MapperConfig(componentModel = "spring", uses = ConversionServiceAdapter.class) -@SpringMapperConfig(conversionServiceBeanName = "myConversionService", generateConverterScan = true) +@SpringMapperConfig(conversionServiceBeanName = "myConversionService") public interface MapperSpringConfig { }