Skip to content

Commit 6072024

Browse files
authored
Fixes #44 (#45)
1 parent 1dea244 commit 6072024

10 files changed

+262
-35
lines changed

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

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import com.squareup.javapoet.TypeName;
55
import org.apache.commons.lang3.tuple.Pair;
66

7+
import javax.lang.model.util.Elements;
78
import java.util.List;
89

910
public class ConversionServiceAdapterDescriptor {
@@ -12,6 +13,27 @@ public class ConversionServiceAdapterDescriptor {
1213
private List<Pair<TypeName, TypeName>> fromToMappings;
1314
private boolean lazyAnnotatedConversionServiceBean;
1415

16+
public Elements getElementUtils() {
17+
return elementUtils;
18+
}
19+
20+
public ConversionServiceAdapterDescriptor elementUtils(Elements elementUtils) {
21+
this.elementUtils = elementUtils;
22+
return this;
23+
}
24+
25+
private Elements elementUtils;
26+
public boolean isSourceVersionAtLeast9() {
27+
return sourceVersionAtLeast9;
28+
}
29+
30+
public ConversionServiceAdapterDescriptor sourceVersionAtLeast9(boolean sourceVersionAtLeast9) {
31+
this.sourceVersionAtLeast9 = sourceVersionAtLeast9;
32+
return this;
33+
}
34+
35+
private boolean sourceVersionAtLeast9;
36+
1537
public ClassName getAdapterClassName() {
1638
return adapterClassName;
1739
}

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

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import java.io.Writer;
1010
import java.time.Clock;
1111
import java.time.ZonedDateTime;
12+
import java.util.Optional;
1213

1314
import static java.time.format.DateTimeFormatter.ISO_INSTANT;
1415
import static java.util.stream.Collectors.toList;
@@ -47,9 +48,11 @@ public void writeConversionServiceAdapter(
4748
private TypeSpec createConversionServiceTypeSpec(
4849
final ConversionServiceAdapterDescriptor descriptor) {
4950
final FieldSpec conversionServiceFieldSpec = buildConversionServiceFieldSpec();
50-
return TypeSpec.classBuilder(descriptor.getAdapterClassName())
51-
.addModifiers(PUBLIC)
52-
.addAnnotation(buildGeneratedAnnotationSpec())
51+
final TypeSpec.Builder adapterClassTypeSpec =
52+
TypeSpec.classBuilder(descriptor.getAdapterClassName()).addModifiers(PUBLIC);
53+
Optional.ofNullable(buildGeneratedAnnotationSpec(descriptor))
54+
.ifPresent(adapterClassTypeSpec::addAnnotation);
55+
return adapterClassTypeSpec
5356
.addAnnotation(ClassName.get("org.springframework.stereotype", "Component"))
5457
.addField(conversionServiceFieldSpec)
5558
.addMethod(buildConstructorSpec(descriptor, conversionServiceFieldSpec))
@@ -163,10 +166,28 @@ private static FieldSpec buildConversionServiceFieldSpec() {
163166
.build();
164167
}
165168

166-
private AnnotationSpec buildGeneratedAnnotationSpec() {
167-
return AnnotationSpec.builder(ClassName.get("javax.annotation", "Generated"))
168-
.addMember("value", "$S", ConversionServiceAdapterGenerator.class.getName())
169-
.addMember("date", "$S", ISO_INSTANT.format(ZonedDateTime.now(clock)))
170-
.build();
169+
private AnnotationSpec buildGeneratedAnnotationSpec(
170+
ConversionServiceAdapterDescriptor descriptor) {
171+
final AnnotationSpec.Builder builder;
172+
if (descriptor.isSourceVersionAtLeast9()
173+
&& isTypeAvailable(descriptor, "javax.annotation.processing.Generated")) {
174+
builder = AnnotationSpec.builder(ClassName.get("javax.annotation.processing", "Generated"));
175+
} else if (isTypeAvailable(descriptor, "javax.annotation.Generated")) {
176+
builder = AnnotationSpec.builder(ClassName.get("javax.annotation", "Generated"));
177+
} else {
178+
builder = null;
179+
}
180+
return Optional.ofNullable(builder)
181+
.map(
182+
build ->
183+
build.addMember("value", "$S", ConversionServiceAdapterGenerator.class.getName()))
184+
.map(build -> build.addMember("date", "$S", ISO_INSTANT.format(ZonedDateTime.now(clock))))
185+
.map(AnnotationSpec.Builder::build)
186+
.orElse(null);
187+
}
188+
189+
private static boolean isTypeAvailable(
190+
final ConversionServiceAdapterDescriptor descriptor, final String name) {
191+
return descriptor.getElementUtils().getTypeElement(name) != null;
171192
}
172193
}

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

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import static java.util.Collections.emptyList;
2525
import static java.util.stream.Collectors.toCollection;
2626
import static java.util.stream.Collectors.toList;
27+
import static javax.lang.model.SourceVersion.RELEASE_8;
2728
import static javax.lang.model.element.ElementKind.METHOD;
2829
import static javax.lang.model.element.Modifier.PUBLIC;
2930
import static javax.lang.model.type.TypeKind.DECLARED;
@@ -73,12 +74,15 @@ private ConversionServiceAdapterDescriptor buildDescriptor(
7374
.conversionServiceBeanName(getConversionServiceName(annotations, roundEnv))
7475
.lazyAnnotatedConversionServiceBean(
7576
getLazyAnnotatedConversionServiceBean(annotations, roundEnv))
76-
.fromToMappings(getExternalConversionMappings(annotations, roundEnv));
77+
.fromToMappings(getExternalConversionMappings(annotations, roundEnv))
78+
.elementUtils(processingEnv.getElementUtils())
79+
.sourceVersionAtLeast9(processingEnv.getSourceVersion().compareTo(RELEASE_8) > 0);
7780
}
7881

7982
private List<Pair<TypeName, TypeName>> getExternalConversionMappings(
8083
final Set<? extends TypeElement> annotations, final RoundEnvironment roundEnv) {
81-
final Optional<List<Pair<TypeName, TypeName>>> pairs = annotations.stream()
84+
final Optional<List<Pair<TypeName, TypeName>>> pairs =
85+
annotations.stream()
8286
.filter(ConverterMapperProcessor::isSpringMapperConfigAnnotation)
8387
.findFirst()
8488
.flatMap(annotation -> findFirstElementAnnotatedWith(roundEnv, annotation))
@@ -89,8 +93,7 @@ private List<Pair<TypeName, TypeName>> getExternalConversionMappings(
8993
.map(AnnotationValue::getValue)
9094
.map(List.class::cast)
9195
.map(this::toSourceTargetTypeNamePairs);
92-
return pairs
93-
.orElse(emptyList());
96+
return pairs.orElse(emptyList());
9497
}
9598

9699
private List<Pair<TypeName, TypeName>> toSourceTargetTypeNamePairs(
@@ -200,7 +203,8 @@ private boolean parameterTypeMatches(final ExecutableElement convert, final Elem
200203
.getTypeUtils()
201204
.isSameType(
202205
getFirstParameterType(convert),
203-
getFirstTypeArgument(getConverterSupertype(mapper).orElseThrow(NoSuchElementException::new)));
206+
getFirstTypeArgument(
207+
getConverterSupertype(mapper).orElseThrow(NoSuchElementException::new)));
204208
}
205209

206210
private static boolean hasExactlyOneParameter(final ExecutableElement convert) {

extensions/src/test/java/org/mapstruct/extensions/spring/converter/ConversionServiceAdapterGeneratorTest.java

Lines changed: 110 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,15 @@
22

33
import com.squareup.javapoet.ClassName;
44
import org.apache.commons.lang3.tuple.Pair;
5+
import org.junit.jupiter.api.BeforeEach;
6+
import org.junit.jupiter.api.Nested;
57
import org.junit.jupiter.api.Test;
8+
import org.junit.jupiter.api.extension.ExtendWith;
9+
import org.mockito.Mock;
10+
import org.mockito.junit.jupiter.MockitoExtension;
611

12+
import javax.lang.model.element.TypeElement;
13+
import javax.lang.model.util.Elements;
714
import java.io.IOException;
815
import java.io.StringWriter;
916
import java.time.Clock;
@@ -14,8 +21,15 @@
1421
import static java.util.Collections.singletonList;
1522
import static org.apache.commons.io.IOUtils.resourceToString;
1623
import static org.assertj.core.api.BDDAssertions.then;
24+
import static org.mockito.BDDMockito.given;
25+
import static org.mockito.Mockito.mock;
1726

27+
@ExtendWith(MockitoExtension.class)
1828
class ConversionServiceAdapterGeneratorTest {
29+
@Mock private Elements elements;
30+
31+
private boolean isAtLeastJava9;
32+
1933
private static final Clock FIXED_CLOCK =
2034
Clock.fixed(
2135
ZonedDateTime.of(2020, 3, 29, 15, 21, 34, (int) (236 * Math.pow(10, 6)), ZoneId.of("Z"))
@@ -24,46 +38,120 @@ class ConversionServiceAdapterGeneratorTest {
2438
private final ConversionServiceAdapterGenerator generator =
2539
new ConversionServiceAdapterGenerator(FIXED_CLOCK);
2640

27-
@Test
28-
void shouldGenerateMatchingOutput() throws IOException {
41+
@Nested
42+
class Java8Generated {
43+
@BeforeEach
44+
void initElements() {
45+
isAtLeastJava9 = false;
46+
given(elements.getTypeElement("javax.annotation.Generated"))
47+
.willReturn(mock(TypeElement.class));
48+
}
49+
50+
@Test
51+
void shouldGenerateMatchingOutput() throws IOException {
52+
ConversionServiceAdapterGeneratorTest.this.shouldGenerateMatchingOutput(
53+
"ConversionServiceAdapterJava8Generated.java");
54+
}
55+
56+
@Test
57+
void shouldGenerateMatchingOutputWhenUsingCustomConversionService() throws IOException {
58+
ConversionServiceAdapterGeneratorTest.this
59+
.shouldGenerateMatchingOutputWhenUsingCustomConversionService(
60+
"ConversionServiceAdapterCustomBeanJava8Generated.java");
61+
}
62+
}
63+
64+
@Nested
65+
class Java9PlusGenerated {
66+
@BeforeEach
67+
void initElements() {
68+
isAtLeastJava9 = true;
69+
given(elements.getTypeElement("javax.annotation.processing.Generated"))
70+
.willReturn(mock(TypeElement.class));
71+
}
72+
73+
@Test
74+
void shouldGenerateMatchingOutput() throws IOException {
75+
ConversionServiceAdapterGeneratorTest.this.shouldGenerateMatchingOutput(
76+
"ConversionServiceAdapterJava9PlusGenerated.java");
77+
}
78+
79+
@Test
80+
void shouldGenerateMatchingOutputWhenUsingCustomConversionService() throws IOException {
81+
ConversionServiceAdapterGeneratorTest.this
82+
.shouldGenerateMatchingOutputWhenUsingCustomConversionService(
83+
"ConversionServiceAdapterCustomBeanJava9PlusGenerated.java");
84+
}
85+
}
86+
87+
@Nested
88+
class NoGenerated {
89+
@BeforeEach
90+
void initElements() {
91+
isAtLeastJava9 = false;
92+
}
93+
94+
@Test
95+
void shouldGenerateMatchingOutput() throws IOException {
96+
ConversionServiceAdapterGeneratorTest.this.shouldGenerateMatchingOutput(
97+
"ConversionServiceAdapterNoGenerated.java");
98+
}
99+
100+
@Test
101+
void shouldGenerateMatchingOutputWhenUsingCustomConversionService() throws IOException {
102+
ConversionServiceAdapterGeneratorTest.this
103+
.shouldGenerateMatchingOutputWhenUsingCustomConversionService(
104+
"ConversionServiceAdapterCustomBeanNoGenerated.java");
105+
}
106+
}
107+
108+
void shouldGenerateMatchingOutput(final String expectedContentFileName) throws IOException {
29109
// Given
30-
final ConversionServiceAdapterDescriptor descriptor = new ConversionServiceAdapterDescriptor();
31-
descriptor.adapterClassName(
32-
ClassName.get(
33-
ConversionServiceAdapterGeneratorTest.class.getPackage().getName(),
34-
"ConversionServiceAdapter"));
35-
descriptor.fromToMappings(
36-
singletonList(Pair.of(ClassName.get("test", "Car"), ClassName.get("test", "CarDto"))));
37-
descriptor.lazyAnnotatedConversionServiceBean(true);
110+
final ConversionServiceAdapterDescriptor descriptor =
111+
new ConversionServiceAdapterDescriptor()
112+
.adapterClassName(
113+
ClassName.get(
114+
ConversionServiceAdapterGeneratorTest.class.getPackage().getName(),
115+
"ConversionServiceAdapter"))
116+
.fromToMappings(
117+
singletonList(
118+
Pair.of(ClassName.get("test", "Car"), ClassName.get("test", "CarDto"))))
119+
.lazyAnnotatedConversionServiceBean(true)
120+
.elementUtils(elements)
121+
.sourceVersionAtLeast9(isAtLeastJava9);
38122
final StringWriter outputWriter = new StringWriter();
39123

40124
// When
41125
generator.writeConversionServiceAdapter(descriptor, outputWriter);
42126

43127
// Then
44128
then(outputWriter.toString())
45-
.isEqualToIgnoringWhitespace(resourceToString("/ConversionServiceAdapter.java", UTF_8));
129+
.isEqualToIgnoringWhitespace(resourceToString('/' + expectedContentFileName, UTF_8));
46130
}
47131

48-
@Test
49-
void shouldGenerateMatchingOutputWhenUsingCustomConversionService() throws IOException {
132+
void shouldGenerateMatchingOutputWhenUsingCustomConversionService(
133+
final String expectedContentFileName) throws IOException {
50134
// Given
51-
final ConversionServiceAdapterDescriptor descriptor = new ConversionServiceAdapterDescriptor();
52-
descriptor.adapterClassName(
53-
ClassName.get(
135+
final ConversionServiceAdapterDescriptor descriptor =
136+
new ConversionServiceAdapterDescriptor()
137+
.adapterClassName(
138+
ClassName.get(
54139
ConversionServiceAdapterGeneratorTest.class.getPackage().getName(),
55-
"ConversionServiceAdapter"));
56-
descriptor.conversionServiceBeanName("myConversionService");
57-
descriptor.fromToMappings(
58-
singletonList(Pair.of(ClassName.get("test", "Car"), ClassName.get("test", "CarDto"))));
59-
descriptor.lazyAnnotatedConversionServiceBean(true);
140+
"ConversionServiceAdapter"))
141+
.conversionServiceBeanName("myConversionService")
142+
.fromToMappings(
143+
singletonList(
144+
Pair.of(ClassName.get("test", "Car"), ClassName.get("test", "CarDto"))))
145+
.lazyAnnotatedConversionServiceBean(true)
146+
.elementUtils(elements)
147+
.sourceVersionAtLeast9(isAtLeastJava9);
60148
final StringWriter outputWriter = new StringWriter();
61149

62150
// When
63151
generator.writeConversionServiceAdapter(descriptor, outputWriter);
64152

65153
// Then
66154
then(outputWriter.toString())
67-
.isEqualToIgnoringWhitespace(resourceToString("/ConversionServiceAdapterCustomBean.java", UTF_8));
155+
.isEqualToIgnoringWhitespace(resourceToString('/' + expectedContentFileName, UTF_8));
68156
}
69157
}

extensions/src/test/resources/ConversionServiceAdapterCustomBean.java renamed to extensions/src/test/resources/ConversionServiceAdapterCustomBeanJava8Generated.java

File renamed without changes.
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package org.mapstruct.extensions.spring.converter;
2+
3+
import javax.annotation.processing.Generated;
4+
import org.springframework.beans.factory.annotation.Qualifier;
5+
import org.springframework.context.annotation.Lazy;
6+
import org.springframework.core.convert.ConversionService;
7+
import org.springframework.stereotype.Component;
8+
import test.Car;
9+
import test.CarDto;
10+
11+
@Generated(
12+
value = "org.mapstruct.extensions.spring.converter.ConversionServiceAdapterGenerator",
13+
date = "2020-03-29T15:21:34.236Z")
14+
@Component
15+
public class ConversionServiceAdapter {
16+
private final ConversionService conversionService;
17+
18+
public ConversionServiceAdapter(
19+
@Qualifier("myConversionService") @Lazy final ConversionService conversionService) {
20+
this.conversionService = conversionService;
21+
}
22+
23+
public CarDto mapCarToCarDto(final Car source) {
24+
return conversionService.convert(source, CarDto.class);
25+
}
26+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package org.mapstruct.extensions.spring.converter;
2+
3+
import org.springframework.beans.factory.annotation.Qualifier;
4+
import org.springframework.context.annotation.Lazy;
5+
import org.springframework.core.convert.ConversionService;
6+
import org.springframework.stereotype.Component;
7+
import test.Car;
8+
import test.CarDto;
9+
10+
@Component
11+
public class ConversionServiceAdapter {
12+
private final ConversionService conversionService;
13+
14+
public ConversionServiceAdapter(
15+
@Qualifier("myConversionService") @Lazy final ConversionService conversionService) {
16+
this.conversionService = conversionService;
17+
}
18+
19+
public CarDto mapCarToCarDto(final Car source) {
20+
return conversionService.convert(source, CarDto.class);
21+
}
22+
}

extensions/src/test/resources/ConversionServiceAdapter.java renamed to extensions/src/test/resources/ConversionServiceAdapterJava8Generated.java

File renamed without changes.
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package org.mapstruct.extensions.spring.converter;
2+
3+
import javax.annotation.processing.Generated;
4+
import org.springframework.context.annotation.Lazy;
5+
import org.springframework.core.convert.ConversionService;
6+
import org.springframework.stereotype.Component;
7+
import test.Car;
8+
import test.CarDto;
9+
10+
@Generated(
11+
value = "org.mapstruct.extensions.spring.converter.ConversionServiceAdapterGenerator",
12+
date = "2020-03-29T15:21:34.236Z")
13+
@Component
14+
public class ConversionServiceAdapter {
15+
private final ConversionService conversionService;
16+
17+
public ConversionServiceAdapter(@Lazy final ConversionService conversionService) {
18+
this.conversionService = conversionService;
19+
}
20+
21+
public CarDto mapCarToCarDto(final Car source) {
22+
return conversionService.convert(source, CarDto.class);
23+
}
24+
}

0 commit comments

Comments
 (0)