|
1 | 1 | package uk.co.kleindelao.mapstruct.spring.converter; |
2 | 2 |
|
3 | | -import com.squareup.javapoet.ClassName; |
4 | | -import org.apache.commons.lang3.tuple.Pair; |
5 | | -import org.springframework.core.convert.converter.Converter; |
6 | | -import uk.co.kleindelao.mapstruct.spring.converter.ConversionServiceBridgeDescriptor.ConversionServiceBridgeDescriptorBuilder; |
| 3 | +import static java.util.stream.Collectors.toList; |
| 4 | +import static javax.lang.model.element.ElementKind.METHOD; |
| 5 | +import static javax.lang.model.element.Modifier.PUBLIC; |
| 6 | +import static javax.lang.model.type.TypeKind.DECLARED; |
| 7 | +import static javax.tools.Diagnostic.Kind.ERROR; |
7 | 8 |
|
| 9 | +import com.squareup.javapoet.ClassName; |
| 10 | +import java.io.IOException; |
| 11 | +import java.io.Writer; |
| 12 | +import java.time.Clock; |
| 13 | +import java.util.List; |
| 14 | +import java.util.Objects; |
| 15 | +import java.util.Optional; |
| 16 | +import java.util.Set; |
8 | 17 | import javax.annotation.processing.AbstractProcessor; |
9 | 18 | import javax.annotation.processing.RoundEnvironment; |
10 | 19 | import javax.annotation.processing.SupportedAnnotationTypes; |
| 20 | +import javax.lang.model.SourceVersion; |
11 | 21 | import javax.lang.model.element.Element; |
12 | 22 | import javax.lang.model.element.ExecutableElement; |
13 | 23 | import javax.lang.model.element.TypeElement; |
14 | 24 | import javax.lang.model.type.DeclaredType; |
15 | 25 | import javax.lang.model.type.TypeMirror; |
16 | 26 | import javax.lang.model.util.Types; |
17 | | -import java.io.IOException; |
18 | | -import java.io.Writer; |
19 | | -import java.time.Clock; |
20 | | -import java.util.Optional; |
21 | | -import java.util.Set; |
22 | | - |
23 | | -import static javax.lang.model.element.ElementKind.METHOD; |
24 | | -import static javax.lang.model.element.Modifier.PUBLIC; |
25 | | -import static javax.lang.model.type.TypeKind.DECLARED; |
26 | | -import static javax.tools.Diagnostic.Kind.ERROR; |
| 27 | +import org.apache.commons.lang3.StringUtils; |
| 28 | +import org.apache.commons.lang3.tuple.MutablePair; |
| 29 | +import org.apache.commons.lang3.tuple.Pair; |
| 30 | +import org.springframework.core.convert.converter.Converter; |
| 31 | +import uk.co.kleindelao.mapstruct.spring.SpringMapperConfig; |
27 | 32 |
|
28 | | -@SupportedAnnotationTypes(ConverterMapperProcessor.ORG_MAPSTRUCT_MAPPER) |
| 33 | +@SupportedAnnotationTypes({ |
| 34 | + ConverterMapperProcessor.MAPPER, |
| 35 | + ConverterMapperProcessor.SPRING_MAPPER_CONFIG |
| 36 | +}) |
29 | 37 | public class ConverterMapperProcessor extends AbstractProcessor { |
30 | | - protected static final String ORG_MAPSTRUCT_MAPPER = "org.mapstruct.Mapper"; |
| 38 | + protected static final String MAPPER = "org.mapstruct.Mapper"; |
| 39 | + protected static final String SPRING_MAPPER_CONFIG = |
| 40 | + "uk.co.kleindelao.mapstruct.spring.SpringMapperConfig"; |
| 41 | + |
| 42 | + private final ConversionServiceAdapterGenerator adapterGenerator; |
31 | 43 |
|
32 | | - private final ConversionServiceBridgeGenerator bridgeGenerator = |
33 | | - new ConversionServiceBridgeGenerator(Clock.systemUTC()); |
| 44 | + public ConverterMapperProcessor() { |
| 45 | + this(new ConversionServiceAdapterGenerator(Clock.systemUTC())); |
| 46 | + } |
| 47 | + |
| 48 | + ConverterMapperProcessor(final ConversionServiceAdapterGenerator adapterGenerator) { |
| 49 | + super(); |
| 50 | + this.adapterGenerator = adapterGenerator; |
| 51 | + } |
| 52 | + |
| 53 | + @Override |
| 54 | + public SourceVersion getSupportedSourceVersion() { |
| 55 | + return SourceVersion.latestSupported(); |
| 56 | + } |
34 | 57 |
|
35 | 58 | @Override |
36 | 59 | public boolean process( |
37 | 60 | final Set<? extends TypeElement> annotations, final RoundEnvironment roundEnv) { |
38 | | - final Types typeUtils = processingEnv.getTypeUtils(); |
39 | | - final ConversionServiceBridgeDescriptorBuilder descriptorBuilder = |
40 | | - ConversionServiceBridgeDescriptor.builder() |
41 | | - .bridgeClassName( |
42 | | - ClassName.get( |
43 | | - ConverterMapperProcessor.class.getPackage().getName(), |
44 | | - "ConversionServiceBridge")); |
45 | | - for (final TypeElement annotation : annotations) { |
46 | | - if (ORG_MAPSTRUCT_MAPPER.contentEquals(annotation.getQualifiedName())) { |
| 61 | + final ConversionServiceAdapterDescriptor descriptor = new ConversionServiceAdapterDescriptor(); |
| 62 | + final Pair<String, String> adapterPackageAndClass = |
| 63 | + getAdapterPackageAndClassName(annotations, roundEnv); |
| 64 | + descriptor.setAdapterClassName( |
| 65 | + ClassName.get(adapterPackageAndClass.getLeft(), adapterPackageAndClass.getRight())); |
| 66 | + annotations.stream() |
| 67 | + .filter(annotation -> MAPPER.contentEquals(annotation.getQualifiedName())) |
| 68 | + .forEach( |
| 69 | + annotation -> |
| 70 | + processMapperAnnotation(roundEnv, descriptor, adapterPackageAndClass, annotation)); |
| 71 | + return false; |
| 72 | + } |
| 73 | + |
| 74 | + private void processMapperAnnotation( |
| 75 | + final RoundEnvironment roundEnv, |
| 76 | + final ConversionServiceAdapterDescriptor descriptor, |
| 77 | + final Pair<String, String> adapterPackageAndClass, |
| 78 | + final TypeElement annotation) { |
| 79 | + final List<Pair<ClassName, ClassName>> fromToMappings = |
47 | 80 | roundEnv.getElementsAnnotatedWith(annotation).stream() |
48 | 81 | .filter(mapper -> mapper.asType().getKind() == DECLARED) |
49 | 82 | .filter(mapper -> getConverterSupertype(mapper).isPresent()) |
50 | | - .forEach( |
51 | | - mapper -> |
52 | | - mapper.getEnclosedElements().stream() |
53 | | - .filter(element -> element.getKind() == METHOD) |
54 | | - .filter(method -> method.getModifiers().contains(PUBLIC)) |
55 | | - .filter(method -> method.getSimpleName().contentEquals("convert")) |
56 | | - .filter( |
57 | | - convert -> ((ExecutableElement) convert).getParameters().size() == 1) |
58 | | - .filter( |
59 | | - convert -> |
60 | | - typeUtils.isSameType( |
61 | | - getFirstParameterType((ExecutableElement) convert), |
62 | | - getFirstTypeArgument(getConverterSupertype(mapper).get()))) |
63 | | - .forEach( |
64 | | - convert -> |
65 | | - descriptorBuilder.fromToMapping( |
66 | | - Pair.of( |
67 | | - (ClassName) |
68 | | - ((ExecutableElement) convert) |
69 | | - .getParameters().stream() |
70 | | - .map(Element::asType) |
71 | | - .map(ClassName::get) |
72 | | - .findFirst() |
73 | | - .get(), |
74 | | - (ClassName) |
75 | | - ClassName.get( |
76 | | - ((ExecutableElement) convert).getReturnType()))))); |
77 | | - try (final Writer outputWriter = |
78 | | - processingEnv |
79 | | - .getFiler() |
80 | | - .createSourceFile( |
81 | | - ConverterMapperProcessor.class.getPackage().getName() + ".ConversionServiceBridge") |
82 | | - .openWriter()) { |
83 | | - bridgeGenerator.writeConversionServiceBridge(descriptorBuilder.build(), outputWriter); |
84 | | - } catch (IOException e) { |
85 | | - processingEnv |
86 | | - .getMessager() |
87 | | - .printMessage( |
88 | | - ERROR, "Error while opening ConversionServiceBridge output file: " + e.getMessage()); |
89 | | - } |
| 83 | + .map(this::toConvertMethod) |
| 84 | + .filter(Objects::nonNull) |
| 85 | + .map(ExecutableElement.class::cast) |
| 86 | + .map(this::toFromToMapping) |
| 87 | + .collect(toList()); |
| 88 | + descriptor.setFromToMappings(fromToMappings); |
| 89 | + writeAdapterClassFile(descriptor, adapterPackageAndClass); |
| 90 | + } |
| 91 | + |
| 92 | + private Pair<ClassName, ClassName> toFromToMapping(final ExecutableElement convert) { |
| 93 | + return Pair.of( |
| 94 | + (ClassName) |
| 95 | + convert.getParameters().stream() |
| 96 | + .map(Element::asType) |
| 97 | + .map(ClassName::get) |
| 98 | + .findFirst() |
| 99 | + .get(), |
| 100 | + (ClassName) ClassName.get(convert.getReturnType())); |
| 101 | + } |
| 102 | + |
| 103 | + private Element toConvertMethod(final Element mapper) { |
| 104 | + return mapper.getEnclosedElements().stream() |
| 105 | + .filter(element -> element.getKind() == METHOD) |
| 106 | + .filter(method -> method.getModifiers().contains(PUBLIC)) |
| 107 | + .filter(method -> method.getSimpleName().contentEquals("convert")) |
| 108 | + .filter(convert -> ((ExecutableElement) convert).getParameters().size() == 1) |
| 109 | + .filter( |
| 110 | + convert -> |
| 111 | + processingEnv |
| 112 | + .getTypeUtils() |
| 113 | + .isSameType( |
| 114 | + getFirstParameterType((ExecutableElement) convert), |
| 115 | + getFirstTypeArgument(getConverterSupertype(mapper).get()))) |
| 116 | + .findFirst() |
| 117 | + .orElse(null); |
| 118 | + } |
| 119 | + |
| 120 | + private void writeAdapterClassFile( |
| 121 | + final ConversionServiceAdapterDescriptor descriptor, |
| 122 | + final Pair<String, String> adapterPackageAndClass) { |
| 123 | + try (final Writer outputWriter = |
| 124 | + processingEnv |
| 125 | + .getFiler() |
| 126 | + .createSourceFile( |
| 127 | + adapterPackageAndClass.getLeft() + "." + adapterPackageAndClass.getRight()) |
| 128 | + .openWriter()) { |
| 129 | + adapterGenerator.writeConversionServiceAdapter(descriptor, outputWriter); |
| 130 | + } catch (IOException e) { |
| 131 | + processingEnv |
| 132 | + .getMessager() |
| 133 | + .printMessage( |
| 134 | + ERROR, |
| 135 | + "Error while opening " |
| 136 | + + adapterPackageAndClass.getRight() |
| 137 | + + " output file: " |
| 138 | + + e.getMessage()); |
| 139 | + } |
| 140 | + } |
| 141 | + |
| 142 | + private Pair<String, String> getAdapterPackageAndClassName( |
| 143 | + final Set<? extends TypeElement> annotations, final RoundEnvironment roundEnv) { |
| 144 | + final MutablePair<String, String> packageAndClass = |
| 145 | + MutablePair.of( |
| 146 | + ConverterMapperProcessor.class.getPackage().getName(), "ConversionServiceAdapter"); |
| 147 | + for (final TypeElement annotation : annotations) { |
| 148 | + if (SPRING_MAPPER_CONFIG.contentEquals(annotation.getQualifiedName())) { |
| 149 | + roundEnv |
| 150 | + .getElementsAnnotatedWith(annotation) |
| 151 | + .forEach(element -> updateFromDeclaration(element, packageAndClass)); |
90 | 152 | } |
91 | 153 | } |
92 | | - return false; |
| 154 | + return packageAndClass; |
| 155 | + } |
| 156 | + |
| 157 | + private void updateFromDeclaration( |
| 158 | + final Element element, final MutablePair<String, String> adapterPackageAndClass) { |
| 159 | + final SpringMapperConfig springMapperConfig = element.getAnnotation(SpringMapperConfig.class); |
| 160 | + adapterPackageAndClass.setLeft( |
| 161 | + Optional.of(springMapperConfig.conversionServiceAdapterPackage()) |
| 162 | + .filter(StringUtils::isNotBlank) |
| 163 | + .orElse( |
| 164 | + String.valueOf( |
| 165 | + processingEnv.getElementUtils().getPackageOf(element).getQualifiedName()))); |
| 166 | + adapterPackageAndClass.setRight(springMapperConfig.conversionServiceAdapterClassName()); |
93 | 167 | } |
94 | 168 |
|
95 | 169 | private Optional<? extends TypeMirror> getConverterSupertype(final Element mapper) { |
|
0 commit comments