Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

90 name collision with same class name in different packages #92

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package org.mapstruct.extensions.spring;

import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.SOURCE;

import java.lang.annotation.Retention;
import java.lang.annotation.Target;

/**
* Overrides the default method name generated in the Adapter class. To be used exclusively on a
* {@link org.springframework.core.convert.converter.Converter} annotated as {@code @Mapper}.
*/
@Target(TYPE)
@Retention(SOURCE)
public @interface AdapterMethodName {
/**
* The method name to be used instead of the default.
*
* @return The method name to be used instead of the default.
*/
String value();
}
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
package org.mapstruct.extensions.spring;

import java.lang.annotation.ElementType;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.SOURCE;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Allows the specification of a conversion that is available via the {@link
* org.springframework.core.convert.ConversionService ConversionService}, but is <em>not</em>
* declared as a MapStruct mapper within the scope of the {@link SpringMapperConfig}.
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
@Target(TYPE)
@Retention(SOURCE)
public @interface ExternalConversion {
Class<?> sourceType();

Class<?> targetType();
String adapterMethodName() default "";
}
46 changes: 44 additions & 2 deletions docs/src/docs/asciidoc/chapter-3-mapper-as-converter.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -142,10 +142,12 @@ public interface MapperSpringConfig {

When the `conversionServiceBeanName` property is set, the built-in <<converterScan>> cannot be used in tests as it does not pick up this property.
However, setting the property `generateConverterScan` to `true` will create an alternative inside the project.
Important to note: This version will _not_ create a `ConversionService` with the given bean name, but merely register all Mappers with the bean identified by the given name. This leads to two practical differences:
Important to note: This version will _not_ create a `ConversionService` with the given bean name, but merely register all Mappers with the bean identified by the given name.
This leads to two practical differences:

- Unlike its <<testExtensions>> counterpart, this version is perfectly suited to be used in production code.
- In a test, the developer will still have to provide a `ConfigurableConversionService` themselves, e.g.:

====
[source,java,linenums]
[subs="verbatim,attributes"]
Expand All @@ -166,4 +168,44 @@ public class ConversionServiceAdapterIntegrationTest {
private ConversionService conversionService;
}
----
====
====

[[adapterMethodName]]
=== Modifying the name for the generated adapter method

By default, the adapter class will contain method names of the form `map<SourceTypeName>To<targetTypeName>`.
If you wish to change this, you can do so on a per-Mapper basis by applying the annotation `@AdapterMethodName`:

====
[source,java,linenums]
[subs="verbatim,attributes"]
----
@Mapper(config = MapperSpringConfig.class)
@AdapterMethodName("toDto")
public interface WheelMapper extends Converter<Wheel, WheelDto> {
@Override
WheelDto convert(Wheel source);
}
----
====

This changes the generated method name to be the annotation's `value` attribute:

====
[source,java,linenums]
[subs="verbatim,attributes"]
----
@Component
public class ConversionServiceAdapter {
private final ConversionService conversionService;

public ConversionServiceAdapter(@Lazy final ConversionService conversionService) {
this.conversionService = conversionService;
}

public WheelDto toDto(final Wheel source) {
return (WheelDto) conversionService.convert(source, TypeDescriptor.valueOf(Wheel.class), TypeDescriptor.valueOf(WheelDto.class));
}
}
----
====
40 changes: 39 additions & 1 deletion docs/src/docs/asciidoc/chapter-4-external-conversions.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,42 @@ public class ConversionServiceAdapter {
}
}
----
====
====

[[externalAdapterMethodName]]
=== Modifying the name for the generated adapter method

By default, the adapter class will contain method names of the form `map<SourceTypeName>To<targetTypeName>`.
If you wish to change this, you can do so on a per-conversion basis by setting the property `adapterMethodName`:

====
[source,java,linenums]
[subs="verbatim,attributes"]
----
@MapperConfig(componentModel = "spring")
@SpringMapperConfig(
externalConversions = @ExternalConversion(sourceType = Blob.class, targetType = byte[].class, adapterMethodName = "blob2Bytes"))
public interface MapstructConfig {}
----
====

This changes the generated method name to be the property's value:

====
[source,java,linenums]
[subs="verbatim,attributes"]
----
@Component
public class ConversionServiceAdapter {
private final ConversionService conversionService;

public ConversionServiceAdapter(@Lazy final ConversionService conversionService) {
this.conversionService = conversionService;
}

public byte[] blob2Bytes(final Blob source) {
return (byte[]) conversionService.convert(source, TypeDescriptor.valueOf(Blob.class), TypeDescriptor.valueOf(byte[].class));
}
}
----
====
1 change: 1 addition & 0 deletions examples/external-conversions/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,6 @@ dependencies {
annotationProcessor libs.mapstruct.processor
implementation libs.spring.context
implementation libs.spring.core
testImplementation libs.mockito
testImplementation libs.spring.test
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,6 @@
@SpringMapperConfig(
externalConversions = {
@ExternalConversion(sourceType = String.class, targetType = Locale.class),
@ExternalConversion(sourceType = Blob.class, targetType = byte[].class)
@ExternalConversion(sourceType = Blob.class, targetType = byte[].class, adapterMethodName = "blob2Bytes")
})
public interface MapstructConfig {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package org.mapstruct.extensions.spring.example.externalconversions;

import static java.nio.charset.StandardCharsets.UTF_8;
import static org.assertj.core.api.BDDAssertions.then;
import static org.mockito.BDDMockito.given;
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 org.springframework.core.convert.TypeDescriptor;

@ExtendWith(MockitoExtension.class)
class ConversionServiceAdapterTest {
@Mock private ConversionService conversionService;

@InjectMocks private ConversionServiceAdapter conversionServiceAdapter;

@Test
void shouldCallConversionServiceFromGeneratedMethodWithOverriddenMethodName() {
final var blob = mock(Blob.class);
final var expectedBytes = "Hello World!".getBytes(UTF_8);
given(
conversionService.convert(
blob, TypeDescriptor.valueOf(Blob.class), TypeDescriptor.valueOf(byte[].class)))
.willReturn(expectedBytes);

final var actualBytes = conversionServiceAdapter.blob2Bytes(blob);

then(actualBytes).isSameAs(expectedBytes);
}
}
2 changes: 2 additions & 0 deletions examples/noconfig/build.gradle
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
dependencies {
annotationProcessor project(":extensions")
implementation projects.examples.model
implementation projects.annotations

testImplementation projects.testExtensions
testImplementation libs.assertj
Expand All @@ -10,5 +11,6 @@ dependencies {
annotationProcessor libs.mapstruct.processor
implementation libs.spring.context
implementation libs.spring.core
testImplementation libs.mockito
testImplementation libs.spring.test
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package org.mapstruct.extensions.spring.example.noconfig;

import org.mapstruct.Mapper;
import org.mapstruct.extensions.spring.AdapterMethodName;
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)
@AdapterMethodName("toDto")
public interface WheelMapper extends Converter<Wheel, WheelDto> {
@Override
WheelDto convert(Wheel source);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package org.mapstruct.extensions.spring.example;

import static org.assertj.core.api.BDDAssertions.then;
import static org.mapstruct.extensions.spring.example.WheelPosition.RIGHT_FRONT;
import static org.mockito.BDDMockito.given;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mapstruct.extensions.spring.converter.ConversionServiceAdapter;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor;

@ExtendWith(MockitoExtension.class)
class ConversionServiceAdapterTest {
@Mock private ConversionService conversionService;

@InjectMocks private ConversionServiceAdapter conversionServiceAdapter;

@Test
void shouldCallConversionServiceFromGeneratedMethodWithOverriddenMethodName() {
final var wheel = new Wheel();
wheel.setPosition(RIGHT_FRONT);
wheel.setDiameter(16);
final var expectedDto = new WheelDto();
expectedDto.setDiameter(16);
expectedDto.setPosition("RIGHT_FRONT");
given(
conversionService.convert(
wheel, TypeDescriptor.valueOf(Wheel.class), TypeDescriptor.valueOf(WheelDto.class)))
.willReturn(expectedDto);

final var actualDto = conversionServiceAdapter.toDto(wheel);

then(actualDto).isSameAs(expectedDto);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,12 @@
import static org.apache.commons.lang3.StringUtils.isNotEmpty;

import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.TypeName;
import java.util.List;
import org.apache.commons.lang3.tuple.Pair;

public class ConversionServiceAdapterDescriptor {
private ClassName adapterClassName;
private String conversionServiceBeanName;
private List<Pair<TypeName, TypeName>> fromToMappings;
private List<FromToMapping> fromToMappings;
private boolean lazyAnnotatedConversionServiceBean;

private boolean generateConverterScan;
Expand Down Expand Up @@ -38,12 +36,12 @@ public ConversionServiceAdapterDescriptor conversionServiceBeanName(
return this;
}

public List<Pair<TypeName, TypeName>> getFromToMappings() {
public List<FromToMapping> getFromToMappings() {
return fromToMappings;
}

public ConversionServiceAdapterDescriptor fromToMappings(
final List<Pair<TypeName, TypeName>> fromToMappings) {
final List<FromToMapping> fromToMappings) {
this.fromToMappings = fromToMappings;
return this;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
import java.util.Collection;
import java.util.Optional;
import java.util.stream.Stream;
import org.apache.commons.lang3.tuple.Pair;

public class ConversionServiceAdapterGenerator extends Generator {
private static final ClassName CONVERSION_SERVICE_CLASS_NAME =
Expand Down Expand Up @@ -151,45 +150,48 @@ private Iterable<MethodSpec> buildMappingMethods(
final FieldSpec injectedConversionServiceFieldSpec) {
return descriptor.getFromToMappings().stream()
.map(
sourceTargetPair ->
toMappingMethodSpec(injectedConversionServiceFieldSpec, sourceTargetPair))
fromToMapping ->
toMappingMethodSpec(injectedConversionServiceFieldSpec, fromToMapping))
.collect(toList());
}

private MethodSpec toMappingMethodSpec(
final FieldSpec injectedConversionServiceFieldSpec,
final Pair<TypeName, TypeName> sourceTargetPair) {
final ParameterSpec sourceParameterSpec = buildSourceParameterSpec(sourceTargetPair.getLeft());
final FromToMapping fromToMapping) {
final ParameterSpec sourceParameterSpec = buildSourceParameterSpec(fromToMapping.getSource());
return MethodSpec.methodBuilder(
String.format(
"map%sTo%s",
collectionOfNameIfApplicable(sourceTargetPair.getLeft()),
collectionOfNameIfApplicable(sourceTargetPair.getRight())))
fromToMapping
.getAdapterMethodName()
.orElse(
String.format(
"map%sTo%s",
collectionOfNameIfApplicable(fromToMapping.getSource()),
collectionOfNameIfApplicable(fromToMapping.getTarget()))))
.addParameter(sourceParameterSpec)
.addModifiers(PUBLIC)
.returns(sourceTargetPair.getRight())
.returns(fromToMapping.getTarget())
.addStatement(
String.format(
"return ($T) $N.convert($N, %s, %s)",
typeDescriptorFormat(sourceTargetPair.getLeft()),
typeDescriptorFormat(sourceTargetPair.getRight())),
typeDescriptorFormat(fromToMapping.getSource()),
typeDescriptorFormat(fromToMapping.getTarget())),
allTypeDescriptorArguments(
injectedConversionServiceFieldSpec, sourceParameterSpec, sourceTargetPair))
injectedConversionServiceFieldSpec, sourceParameterSpec, fromToMapping))
.build();
}

private Object[] allTypeDescriptorArguments(
final FieldSpec injectedConversionServiceFieldSpec,
final ParameterSpec sourceParameterSpec,
final Pair<TypeName, TypeName> sourceTargetPair) {
final FromToMapping fromToMapping) {
return concat(
concat(
Stream.of(
sourceTargetPair.getRight(),
fromToMapping.getTarget(),
injectedConversionServiceFieldSpec,
sourceParameterSpec),
typeDescriptorArguments(sourceTargetPair.getLeft())),
typeDescriptorArguments(sourceTargetPair.getRight()))
typeDescriptorArguments(fromToMapping.getSource())),
typeDescriptorArguments(fromToMapping.getTarget()))
.toArray();
}

Expand Down
Loading