Skip to content

Commit bfefe88

Browse files
authored
Fixes #54 (#55)
We now extract the parameter types directly from the generic declaration and don't look for the convert's method parameters, thus allowing us to add the very simple mappers without explicit convert methods to the generated Adapter.
1 parent dbd5fda commit bfefe88

File tree

11 files changed

+243
-55
lines changed

11 files changed

+243
-55
lines changed
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
dependencies {
2+
annotationProcessor project(":extensions")
3+
implementation projects.annotations
4+
5+
testImplementation libs.assertj
6+
testImplementation libs.bundles.junit.jupiter
7+
implementation libs.mapstruct.core
8+
annotationProcessor libs.mapstruct.processor
9+
implementation libs.spring.context
10+
implementation libs.spring.core
11+
testImplementation libs.spring.test
12+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package org.mapstruct.extensions.spring.example.noexplicitconvert;
2+
3+
public class LineDto {
4+
private PointDto origin;
5+
private PointDto destination;
6+
7+
public PointDto getOrigin() {
8+
return origin;
9+
}
10+
11+
public void setOrigin(PointDto origin) {
12+
this.origin = origin;
13+
}
14+
15+
public PointDto getDestination() {
16+
return destination;
17+
}
18+
19+
public void setDestination(PointDto destination) {
20+
this.destination = destination;
21+
}
22+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package org.mapstruct.extensions.spring.example.noexplicitconvert;
2+
3+
import org.mapstruct.MapperConfig;
4+
import org.mapstruct.extensions.spring.converter.ConversionServiceAdapter;
5+
6+
@MapperConfig(componentModel = "spring", uses= ConversionServiceAdapter.class)
7+
public interface MapstructConfig {}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package org.mapstruct.extensions.spring.example.noexplicitconvert;
2+
3+
public class PointDto {
4+
private int x;
5+
private int y;
6+
7+
public int getX() {
8+
return x;
9+
}
10+
11+
public void setX(int x) {
12+
this.x = x;
13+
}
14+
15+
public int getY() {
16+
return y;
17+
}
18+
19+
public void setY(int y) {
20+
this.y = y;
21+
}
22+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package org.mapstruct.extensions.spring.example.noexplicitconvert;
2+
3+
public class SimpleLine {
4+
private SimplePoint origin;
5+
private SimplePoint destination;
6+
7+
public SimplePoint getOrigin() {
8+
return origin;
9+
}
10+
11+
public void setOrigin(SimplePoint origin) {
12+
this.origin = origin;
13+
}
14+
15+
public SimplePoint getDestination() {
16+
return destination;
17+
}
18+
19+
public void setDestination(SimplePoint destination) {
20+
this.destination = destination;
21+
}
22+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package org.mapstruct.extensions.spring.example.noexplicitconvert;
2+
3+
import org.mapstruct.Mapper;
4+
import org.springframework.core.convert.converter.Converter;
5+
6+
@Mapper(config = MapstructConfig.class)
7+
public interface SimpleLineMapper extends Converter<SimpleLine, LineDto> {
8+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package org.mapstruct.extensions.spring.example.noexplicitconvert;
2+
3+
public class SimplePoint {
4+
private int x;
5+
private int y;
6+
7+
public int getX() {
8+
return x;
9+
}
10+
11+
public int getY() {
12+
return y;
13+
}
14+
15+
public void setX(int x) {
16+
this.x = x;
17+
}
18+
19+
public void setY(int y) {
20+
this.y = y;
21+
}
22+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package org.mapstruct.extensions.spring.example.noexplicitconvert;
2+
3+
import org.mapstruct.Mapper;
4+
import org.springframework.core.convert.converter.Converter;
5+
6+
@Mapper(config = MapstructConfig.class)
7+
public interface SimplePointMapper extends Converter<SimplePoint, PointDto> {
8+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
package org.mapstruct.extensions.spring.example.noexplicitconvert;
2+
3+
import org.junit.jupiter.api.BeforeEach;
4+
import org.junit.jupiter.api.Test;
5+
import org.junit.jupiter.api.extension.ExtendWith;
6+
import org.mapstruct.extensions.spring.converter.ConversionServiceAdapter;
7+
import org.springframework.beans.factory.annotation.Autowired;
8+
import org.springframework.context.annotation.Bean;
9+
import org.springframework.context.annotation.ComponentScan;
10+
import org.springframework.core.convert.support.ConfigurableConversionService;
11+
import org.springframework.core.convert.support.DefaultConversionService;
12+
import org.springframework.stereotype.Component;
13+
import org.springframework.test.context.ContextConfiguration;
14+
import org.springframework.test.context.junit.jupiter.SpringExtension;
15+
16+
import static org.assertj.core.api.BDDAssertions.then;
17+
18+
@ExtendWith(SpringExtension.class)
19+
@ContextConfiguration(
20+
classes = {ConversionServiceAdapterIntegrationTest.AdditionalBeanConfiguration.class})
21+
public class ConversionServiceAdapterIntegrationTest {
22+
private static final int TEST_ORIGIN_X = 5;
23+
private static final int TEST_ORIGIN_Y = 20;
24+
private static final int TEST_DESTINATION_X = 30;
25+
private static final int TEST_DESTINATION_Y = 23;
26+
27+
@Autowired private SimplePointMapper pointMapper;
28+
@Autowired private SimpleLineMapper lineMapper;
29+
@Autowired private ConfigurableConversionService conversionService;
30+
31+
@ComponentScan("org.mapstruct.extensions.spring")
32+
@Component
33+
static class AdditionalBeanConfiguration {
34+
@Bean
35+
ConfigurableConversionService getConversionService() {
36+
return new DefaultConversionService();
37+
}
38+
}
39+
40+
@BeforeEach
41+
void addMappersToConversionService() {
42+
conversionService.addConverter(pointMapper);
43+
conversionService.addConverter(lineMapper);
44+
}
45+
46+
@Test
47+
void shouldKnowAllMappers() {
48+
then(conversionService.canConvert(SimpleLine.class, LineDto.class)).isTrue();
49+
then(conversionService.canConvert(SimplePoint.class, PointDto.class)).isTrue();
50+
}
51+
52+
@Test
53+
void shouldMapAllAttributes() {
54+
// Given
55+
final SimplePoint origin = origin();
56+
final SimplePoint destination = destination();
57+
final SimpleLine sourceLine = simpleLine(origin, destination);
58+
59+
// When
60+
final LineDto mappedLine = conversionService.convert(sourceLine, LineDto.class);
61+
62+
// Then
63+
then(mappedLine).isNotNull();
64+
then(mappedLine.getOrigin().getX()).isEqualTo(TEST_ORIGIN_X);
65+
then(mappedLine.getOrigin().getY()).isEqualTo(TEST_ORIGIN_Y);
66+
then(mappedLine.getDestination().getX()).isEqualTo(TEST_DESTINATION_X);
67+
then(mappedLine.getDestination().getY()).isEqualTo(TEST_DESTINATION_Y);
68+
}
69+
70+
private static SimpleLine simpleLine(SimplePoint origin, SimplePoint destination) {
71+
final SimpleLine sourceLine = new SimpleLine();
72+
sourceLine.setOrigin(origin);
73+
sourceLine.setDestination(destination);
74+
return sourceLine;
75+
}
76+
77+
private static SimplePoint destination() {
78+
return simplePoint(TEST_DESTINATION_X, TEST_DESTINATION_Y);
79+
}
80+
81+
private static SimplePoint origin() {
82+
return simplePoint(TEST_ORIGIN_X, TEST_ORIGIN_Y);
83+
}
84+
85+
private static SimplePoint simplePoint(int testOriginX, int testOriginY) {
86+
final SimplePoint origin = new SimplePoint();
87+
origin.setX(testOriginX);
88+
origin.setY(testOriginY);
89+
return origin;
90+
}
91+
92+
@Test
93+
void shouldHaveMethodsForAllConverters() {
94+
// Given
95+
final var conversionServiceAdapter = new ConversionServiceAdapter(conversionService);
96+
final var origin = origin();
97+
final var destination = destination();
98+
final var sourceLine = simpleLine(origin, destination);
99+
100+
// When
101+
final var pointDto = conversionServiceAdapter.mapSimplePointToPointDto(simplePoint(TEST_ORIGIN_X, TEST_DESTINATION_Y));
102+
final var lineDto = conversionServiceAdapter.mapSimpleLineToLineDto(sourceLine);
103+
104+
// Then
105+
then(pointDto).isNotNull();
106+
then(pointDto.getX()).isEqualTo(TEST_ORIGIN_X);
107+
then(pointDto.getY()).isEqualTo(TEST_DESTINATION_Y);
108+
then(lineDto).isNotNull();
109+
then(lineDto.getOrigin().getX()).isEqualTo(TEST_ORIGIN_X);
110+
then(lineDto.getOrigin().getY()).isEqualTo(TEST_ORIGIN_Y);
111+
then(lineDto.getDestination().getX()).isEqualTo(TEST_DESTINATION_X);
112+
then(lineDto.getDestination().getY()).isEqualTo(TEST_DESTINATION_Y);
113+
}
114+
}

extensions/src/main/java/org/mapstruct/extensions/spring/converter/ConverterMapperProcessor.java

Lines changed: 5 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,6 @@
2525
import static java.util.stream.Collectors.toCollection;
2626
import static java.util.stream.Collectors.toList;
2727
import static javax.lang.model.SourceVersion.RELEASE_8;
28-
import static javax.lang.model.element.ElementKind.METHOD;
29-
import static javax.lang.model.element.Modifier.PUBLIC;
3028
import static javax.lang.model.type.TypeKind.DECLARED;
3129
import static javax.tools.Diagnostic.Kind.ERROR;
3230

@@ -158,9 +156,8 @@ private void processMapperAnnotation(
158156
roundEnv.getElementsAnnotatedWith(annotation).stream()
159157
.filter(ConverterMapperProcessor::isKindDeclared)
160158
.filter(this::hasConverterSupertype)
161-
.map(this::toConvertMethod)
159+
.map(this::toTypeArguments)
162160
.filter(Objects::nonNull)
163-
.map(ExecutableElement.class::cast)
164161
.map(ConverterMapperProcessor::toFromToMapping)
165162
.collect(toCollection(ArrayList::new));
166163
fromToMappings.addAll(descriptor.getFromToMappings());
@@ -176,57 +173,18 @@ private static boolean isKindDeclared(Element mapper) {
176173
return mapper.asType().getKind() == DECLARED;
177174
}
178175

179-
private static Pair<TypeName, TypeName> toFromToMapping(final ExecutableElement convert) {
180-
return Pair.of(
181-
convert.getParameters().stream()
182-
.map(Element::asType)
183-
.map(TypeName::get)
184-
.findFirst()
185-
.orElseThrow(NoSuchElementException::new),
186-
TypeName.get(convert.getReturnType()));
187-
}
188-
189-
private Element toConvertMethod(final Element mapper) {
190-
return mapper.getEnclosedElements().stream()
191-
.filter(ConverterMapperProcessor::isAMethod)
192-
.filter(ConverterMapperProcessor::isPublic)
193-
.filter(ConverterMapperProcessor::hasNameConvert)
194-
.map(ExecutableElement.class::cast)
195-
.filter(ConverterMapperProcessor::hasExactlyOneParameter)
196-
.filter(convert -> parameterTypeMatches(convert, mapper))
197-
.findFirst()
198-
.orElse(null);
199-
}
200-
201-
private boolean parameterTypeMatches(final ExecutableElement convert, final Element mapper) {
202-
return processingEnv
203-
.getTypeUtils()
204-
.isSameType(
205-
getFirstParameterType(convert),
206-
getFirstTypeArgument(
207-
getConverterSupertype(mapper).orElseThrow(NoSuchElementException::new)));
208-
}
209-
210-
private static boolean hasExactlyOneParameter(final ExecutableElement convert) {
211-
return convert.getParameters().size() == 1;
176+
private static Pair<TypeName, TypeName> toFromToMapping(final List<? extends TypeMirror> sourceTypeTargetType) {
177+
return Pair.of(TypeName.get(sourceTypeTargetType.get(0)), TypeName.get(sourceTypeTargetType.get(1)));
212178
}
213179

214-
private static boolean hasNameConvert(final Element method) {
215-
return hasName(method.getSimpleName(), "convert");
180+
private List<? extends TypeMirror> toTypeArguments(final Element mapper) {
181+
return ((DeclaredType) getConverterSupertype(mapper).orElseThrow()).getTypeArguments();
216182
}
217183

218184
private static boolean hasName(final Name name, final String comparisonName) {
219185
return name.contentEquals(comparisonName);
220186
}
221187

222-
private static boolean isPublic(final Element method) {
223-
return method.getModifiers().contains(PUBLIC);
224-
}
225-
226-
private static boolean isAMethod(final Element element) {
227-
return element.getKind() == METHOD;
228-
}
229-
230188
private void writeAdapterClassFile(final ConversionServiceAdapterDescriptor descriptor) {
231189
try (final Writer outputWriter = openAdapterFile(descriptor)) {
232190
adapterGenerator.writeConversionServiceAdapter(descriptor, outputWriter);
@@ -357,12 +315,4 @@ private boolean isSpringConverterType(final TypeMirror supertype) {
357315
.toString()
358316
.equals(SPRING_CONVERTER_FULL_NAME);
359317
}
360-
361-
private static TypeMirror getFirstParameterType(final ExecutableElement convert) {
362-
return convert.getParameters().stream().findFirst().map(Element::asType).orElse(null);
363-
}
364-
365-
private static TypeMirror getFirstTypeArgument(final TypeMirror converterSupertype) {
366-
return ((DeclaredType) converterSupertype).getTypeArguments().stream().findFirst().orElse(null);
367-
}
368318
}

0 commit comments

Comments
 (0)