Skip to content

Commit 700fe9a

Browse files
authored
We now handle array types. (#37)
Fixed #36.
1 parent 62c7343 commit 700fe9a

File tree

7 files changed

+230
-108
lines changed

7 files changed

+230
-108
lines changed

examples/arrays/build.gradle

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+
implementation libs.spring.context
6+
implementation libs.spring.core
7+
implementation libs.mapstruct.core
8+
annotationProcessor libs.mapstruct.processor
9+
10+
testImplementation libs.bundles.junit.jupiter
11+
testImplementation libs.mockito
12+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package org.mapstruct.extensions.spring.example.arrays;
2+
3+
import org.mapstruct.Mapper;
4+
import org.springframework.core.convert.converter.Converter;
5+
6+
import java.io.BufferedInputStream;
7+
import java.io.BufferedOutputStream;
8+
import java.io.ByteArrayOutputStream;
9+
import java.io.IOException;
10+
import java.sql.Blob;
11+
import java.sql.SQLException;
12+
13+
@Mapper(config = SpringMapperConfig.class)
14+
public interface BlobToByteArrayMapper extends Converter<Blob, byte[]> {
15+
@Override
16+
default byte[] convert(Blob blob) {
17+
try (BufferedInputStream bis = new BufferedInputStream(blob.getBinaryStream());
18+
ByteArrayOutputStream baos = new ByteArrayOutputStream();
19+
BufferedOutputStream bos = new BufferedOutputStream(baos)) {
20+
byte[] buffer = new byte[4096];
21+
boolean keepReading;
22+
do {
23+
int bytesRead = bis.read(buffer);
24+
keepReading = bytesRead != -1;
25+
if(keepReading) {
26+
bos.write(buffer, 0, bytesRead);
27+
}
28+
} while (keepReading);
29+
30+
return baos.toByteArray();
31+
} catch (SQLException | IOException e) {
32+
throw new RuntimeException(e);
33+
}
34+
}
35+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package org.mapstruct.extensions.spring.example.arrays;
2+
3+
import org.mapstruct.MapperConfig;
4+
5+
@MapperConfig(componentModel = "spring")
6+
public interface SpringMapperConfig {
7+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package org.mapstruct.extensions.spring.converter;
2+
3+
import org.junit.jupiter.api.Test;
4+
import org.junit.jupiter.api.extension.ExtendWith;
5+
import org.mockito.InjectMocks;
6+
import org.mockito.Mock;
7+
import org.mockito.junit.jupiter.MockitoExtension;
8+
import org.springframework.core.convert.ConversionService;
9+
10+
import java.sql.Blob;
11+
12+
import static org.mockito.BDDMockito.then;
13+
import static org.mockito.Mockito.mock;
14+
15+
@ExtendWith(MockitoExtension.class)
16+
class ConversionServiceAdapterTest {
17+
@Mock
18+
private ConversionService conversionService;
19+
20+
@InjectMocks
21+
private ConversionServiceAdapter conversionServiceAdapter;
22+
23+
@Test
24+
void shouldMapViaConversionServiceInGeneratedMethod() {
25+
// Given
26+
final Blob blob = mock(Blob.class);
27+
28+
// When
29+
conversionServiceAdapter.mapBlobToArrayOfbyte(blob);
30+
31+
// Then
32+
then(conversionService).should().convert(blob, byte[].class);
33+
}
34+
}

examples/external-conversions/src/main/java/org/mapstruct/extensions/spring/example/externalconversions/MapstructConfig.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,13 @@
44
import org.mapstruct.extensions.spring.ExternalConversion;
55
import org.mapstruct.extensions.spring.SpringMapperConfig;
66

7+
import java.sql.Blob;
78
import java.util.Locale;
89

910
@MapperConfig(componentModel = "spring")
1011
@SpringMapperConfig(
11-
externalConversions = @ExternalConversion(sourceType = String.class, targetType = Locale.class))
12+
externalConversions = {
13+
@ExternalConversion(sourceType = String.class, targetType = Locale.class),
14+
@ExternalConversion(sourceType = Blob.class, targetType = byte[].class)
15+
})
1216
public interface MapstructConfig {}

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

Lines changed: 136 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -14,116 +14,145 @@
1414
import static javax.lang.model.element.Modifier.*;
1515

1616
public class ConversionServiceAdapterGenerator {
17-
private final Clock clock;
18-
19-
public ConversionServiceAdapterGenerator(final Clock clock) {
20-
this.clock = clock;
21-
}
22-
23-
public void writeConversionServiceAdapter(
24-
ConversionServiceAdapterDescriptor descriptor, Writer out) {
25-
try {
26-
JavaFile.builder(
27-
descriptor.getAdapterClassName().packageName(),
28-
createConversionServiceTypeSpec(descriptor))
29-
.build()
30-
.writeTo(out);
31-
} catch (IOException e) {
32-
throw new UncheckedIOException(e);
33-
}
17+
private final Clock clock;
18+
19+
public ConversionServiceAdapterGenerator(final Clock clock) {
20+
this.clock = clock;
21+
}
22+
23+
public void writeConversionServiceAdapter(
24+
ConversionServiceAdapterDescriptor descriptor, Writer out) {
25+
try {
26+
JavaFile.builder(
27+
descriptor.getAdapterClassName().packageName(),
28+
createConversionServiceTypeSpec(descriptor))
29+
.build()
30+
.writeTo(out);
31+
} catch (IOException e) {
32+
throw new UncheckedIOException(e);
3433
}
35-
36-
private TypeSpec createConversionServiceTypeSpec(
37-
final ConversionServiceAdapterDescriptor descriptor) {
38-
final FieldSpec conversionServiceFieldSpec = buildConversionServiceFieldSpec();
39-
return TypeSpec.classBuilder(descriptor.getAdapterClassName())
40-
.addModifiers(PUBLIC)
41-
.addAnnotation(buildGeneratedAnnotationSpec())
42-
.addAnnotation(ClassName.get("org.springframework.stereotype", "Component"))
43-
.addField(conversionServiceFieldSpec)
44-
.addMethod(buildConstructorSpec(descriptor, conversionServiceFieldSpec))
45-
.addMethods(buildMappingMethods(descriptor, conversionServiceFieldSpec))
46-
.build();
34+
}
35+
36+
private TypeSpec createConversionServiceTypeSpec(
37+
final ConversionServiceAdapterDescriptor descriptor) {
38+
final FieldSpec conversionServiceFieldSpec = buildConversionServiceFieldSpec();
39+
return TypeSpec.classBuilder(descriptor.getAdapterClassName())
40+
.addModifiers(PUBLIC)
41+
.addAnnotation(buildGeneratedAnnotationSpec())
42+
.addAnnotation(ClassName.get("org.springframework.stereotype", "Component"))
43+
.addField(conversionServiceFieldSpec)
44+
.addMethod(buildConstructorSpec(descriptor, conversionServiceFieldSpec))
45+
.addMethods(buildMappingMethods(descriptor, conversionServiceFieldSpec))
46+
.build();
47+
}
48+
49+
private static MethodSpec buildConstructorSpec(
50+
final ConversionServiceAdapterDescriptor descriptor,
51+
final FieldSpec conversionServiceFieldSpec) {
52+
final ParameterSpec constructorParameterSpec =
53+
buildConstructorParameterSpec(descriptor, conversionServiceFieldSpec);
54+
return MethodSpec.constructorBuilder()
55+
.addModifiers(PUBLIC)
56+
.addParameter(constructorParameterSpec)
57+
.addStatement("this.$N = $N", conversionServiceFieldSpec, constructorParameterSpec)
58+
.build();
59+
}
60+
61+
private static ParameterSpec buildConstructorParameterSpec(
62+
final ConversionServiceAdapterDescriptor descriptor,
63+
final FieldSpec conversionServiceFieldSpec) {
64+
final ParameterSpec.Builder parameterBuilder =
65+
ParameterSpec.builder(
66+
conversionServiceFieldSpec.type, conversionServiceFieldSpec.name, FINAL);
67+
if (StringUtils.isNotEmpty(descriptor.getConversionServiceBeanName())) {
68+
parameterBuilder.addAnnotation(buildQualifierANnotation(descriptor));
4769
}
48-
49-
private static MethodSpec buildConstructorSpec(final ConversionServiceAdapterDescriptor descriptor, final FieldSpec conversionServiceFieldSpec) {
50-
final ParameterSpec constructorParameterSpec = buildConstructorParameterSpec(descriptor, conversionServiceFieldSpec);
51-
return MethodSpec.constructorBuilder().addModifiers(PUBLIC).addParameter(constructorParameterSpec).addStatement("this.$N = $N", conversionServiceFieldSpec, constructorParameterSpec).build();
70+
if (Boolean.TRUE.equals(descriptor.isLazyAnnotatedConversionServiceBean())) {
71+
parameterBuilder.addAnnotation(buildLazyAnnotation());
5272
}
53-
54-
private static ParameterSpec buildConstructorParameterSpec(final ConversionServiceAdapterDescriptor descriptor, final FieldSpec conversionServiceFieldSpec) {
55-
final ParameterSpec.Builder parameterBuilder = ParameterSpec.builder(conversionServiceFieldSpec.type, conversionServiceFieldSpec.name, FINAL);
56-
if (StringUtils.isNotEmpty(descriptor.getConversionServiceBeanName())) {
57-
parameterBuilder.addAnnotation(buildQualifierANnotation(descriptor));
58-
}
59-
if (Boolean.TRUE.equals(descriptor.isLazyAnnotatedConversionServiceBean())) {
60-
parameterBuilder.addAnnotation(buildLazyAnnotation());
61-
}
62-
return parameterBuilder.build();
73+
return parameterBuilder.build();
74+
}
75+
76+
private static AnnotationSpec buildQualifierANnotation(
77+
ConversionServiceAdapterDescriptor descriptor) {
78+
return AnnotationSpec.builder(
79+
ClassName.get("org.springframework.beans.factory.annotation", "Qualifier"))
80+
.addMember("value", "$S", descriptor.getConversionServiceBeanName())
81+
.build();
82+
}
83+
84+
private static AnnotationSpec buildLazyAnnotation() {
85+
return AnnotationSpec.builder(ClassName.get("org.springframework.context.annotation", "Lazy"))
86+
.build();
87+
}
88+
89+
private static String simpleName(final TypeName typeName) {
90+
final TypeName rawType = rawType(typeName);
91+
if (rawType instanceof ArrayTypeName) {
92+
return arraySimpleName((ArrayTypeName) rawType);
93+
} else if (rawType instanceof ClassName) {
94+
return ((ClassName)rawType).simpleName();
6395
}
64-
65-
private static AnnotationSpec buildQualifierANnotation(ConversionServiceAdapterDescriptor descriptor) {
66-
return AnnotationSpec
67-
.builder(ClassName.get("org.springframework.beans.factory.annotation", "Qualifier"))
68-
.addMember("value", "$S", descriptor.getConversionServiceBeanName())
69-
.build();
70-
}
71-
72-
private static AnnotationSpec buildLazyAnnotation() {
73-
return AnnotationSpec
74-
.builder(ClassName.get("org.springframework.context.annotation","Lazy"))
75-
.build();
76-
}
77-
78-
private static String simpleName(final TypeName typeName) {
79-
return rawType(typeName).simpleName();
80-
}
81-
82-
private static ClassName rawType(final TypeName typeName) {
83-
if (typeName instanceof ParameterizedTypeName) {
84-
return ((ParameterizedTypeName)typeName).rawType;
85-
}
86-
return (ClassName) typeName;
87-
}
88-
89-
private static Iterable<MethodSpec> buildMappingMethods(
90-
final ConversionServiceAdapterDescriptor descriptor,
91-
final FieldSpec injectedConversionServiceFieldSpec) {
92-
return descriptor.getFromToMappings().stream()
93-
.map(
94-
sourceTargetPair -> {
95-
final ParameterSpec sourceParameterSpec =
96-
buildSourceParameterSpec(sourceTargetPair.getLeft());
97-
return MethodSpec.methodBuilder(
98-
"map"
99-
+ simpleName(sourceTargetPair.getLeft())
100-
+ "To"
101-
+ simpleName(sourceTargetPair.getRight()))
102-
.addParameter(sourceParameterSpec)
103-
.addModifiers(PUBLIC)
104-
.returns(sourceTargetPair.getRight())
105-
.addStatement(
106-
"return $N.convert($N, $T.class)",
107-
injectedConversionServiceFieldSpec,
108-
sourceParameterSpec,
109-
rawType(sourceTargetPair.getRight()))
110-
.build();
111-
})
112-
.collect(toList());
113-
}
114-
115-
private static ParameterSpec buildSourceParameterSpec(final TypeName sourceClassName) {
116-
return ParameterSpec.builder(sourceClassName, "source", FINAL).build();
117-
}
118-
119-
private static FieldSpec buildConversionServiceFieldSpec() {
120-
return FieldSpec.builder(ClassName.get("org.springframework.core.convert", "ConversionService"), "conversionService", PRIVATE, FINAL).build();
121-
}
122-
123-
private AnnotationSpec buildGeneratedAnnotationSpec() {
124-
return AnnotationSpec.builder(ClassName.get("javax.annotation", "Generated"))
125-
.addMember("value", "$S", ConversionServiceAdapterGenerator.class.getName())
126-
.addMember("date", "$S", DateTimeFormatter.ISO_INSTANT.format(ZonedDateTime.now(clock)))
127-
.build();
96+
else return String.valueOf(typeName);
97+
}
98+
99+
private static String arraySimpleName(ArrayTypeName arrayTypeName) {
100+
return "ArrayOf"
101+
+ (arrayTypeName.componentType instanceof ArrayTypeName
102+
? arraySimpleName((ArrayTypeName) arrayTypeName.componentType)
103+
: arrayTypeName.componentType);
104+
}
105+
106+
private static TypeName rawType(final TypeName typeName) {
107+
if (typeName instanceof ParameterizedTypeName) {
108+
return ((ParameterizedTypeName) typeName).rawType;
128109
}
110+
return typeName;
111+
}
112+
113+
private static Iterable<MethodSpec> buildMappingMethods(
114+
final ConversionServiceAdapterDescriptor descriptor,
115+
final FieldSpec injectedConversionServiceFieldSpec) {
116+
return descriptor.getFromToMappings().stream()
117+
.map(
118+
sourceTargetPair -> {
119+
final ParameterSpec sourceParameterSpec =
120+
buildSourceParameterSpec(sourceTargetPair.getLeft());
121+
return MethodSpec.methodBuilder(
122+
"map"
123+
+ simpleName(sourceTargetPair.getLeft())
124+
+ "To"
125+
+ simpleName(sourceTargetPair.getRight()))
126+
.addParameter(sourceParameterSpec)
127+
.addModifiers(PUBLIC)
128+
.returns(sourceTargetPair.getRight())
129+
.addStatement(
130+
"return $N.convert($N, $T.class)",
131+
injectedConversionServiceFieldSpec,
132+
sourceParameterSpec,
133+
rawType(sourceTargetPair.getRight()))
134+
.build();
135+
})
136+
.collect(toList());
137+
}
138+
139+
private static ParameterSpec buildSourceParameterSpec(final TypeName sourceClassName) {
140+
return ParameterSpec.builder(sourceClassName, "source", FINAL).build();
141+
}
142+
143+
private static FieldSpec buildConversionServiceFieldSpec() {
144+
return FieldSpec.builder(
145+
ClassName.get("org.springframework.core.convert", "ConversionService"),
146+
"conversionService",
147+
PRIVATE,
148+
FINAL)
149+
.build();
150+
}
151+
152+
private AnnotationSpec buildGeneratedAnnotationSpec() {
153+
return AnnotationSpec.builder(ClassName.get("javax.annotation", "Generated"))
154+
.addMember("value", "$S", ConversionServiceAdapterGenerator.class.getName())
155+
.addMember("date", "$S", DateTimeFormatter.ISO_INSTANT.format(ZonedDateTime.now(clock)))
156+
.build();
157+
}
129158
}

settings.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ include "examples:packagename-and-classname"
1111
include "examples:custom-conversion-service-bean"
1212
include "examples:simple-springboot"
1313
include "examples:external-conversions"
14+
include "examples:arrays"
1415
include "docs"
1516

1617
enableFeaturePreview("VERSION_CATALOGS")

0 commit comments

Comments
 (0)