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