Merge pull request !1 from easii/1.3.2
This commit is contained in:
easii 2023-06-18 16:18:18 +00:00 committed by Gitee
commit 3a9206cdeb
No known key found for this signature in database
GPG Key ID: 173E9B9CA92EEF8F
20 changed files with 425 additions and 125 deletions

View File

@ -61,7 +61,7 @@ public class User {
```xml ```xml
<properties> <properties>
<mapstruct-plus.version>1.3.1</mapstruct-plus.version> <mapstruct-plus.version>1.3.2</mapstruct-plus.version>
</properties> </properties>
<dependencies> <dependencies>
<dependency> <dependency>

View File

@ -56,27 +56,22 @@ copyright: false
<dependency> <dependency>
<groupId>io.github.linpeilie</groupId> <groupId>io.github.linpeilie</groupId>
<artifactId>mapstruct-plus-spring-boot-starter</artifactId> <artifactId>mapstruct-plus-spring-boot-starter</artifactId>
<version>1.3.1</version> <version>1.3.2</version>
</dependency> </dependency>
``` ```
- gradle - gradle
```groovy ```groovy
implementation group: 'io.github.linpeilie', name: 'mapstruct-plus-spring-boot-starter', version: '1.3.1' implementation group: 'io.github.linpeilie', name: 'mapstruct-plus-spring-boot-starter', version: '1.3.2'
``` ```
## 更新日志 ## 更新日志
### 1.3.1 ### 1.3.2
- 增加编译参数中指定配置类的功能 - 不可变对象支持,可以使用任意包下的 `Immutable` 标注类型为不可变类
- 更好地适配 IDEA 部分编译场景 - 全面适配 IDEA 部分编译问题,使用更加流畅丝滑
### 1.3.0
- fix: 解决本地开发时 IDEA 编译或者运行时报错等与预期不一致的问题
- feature: AutoMapper 注解增加 imports 属性支持
…… ……

View File

@ -514,3 +514,17 @@ public class CarDtoToCarMapperImpl implements CarDtoToCarMapper {
如果还是不理解,这里可以认为,该注解就是本该应用在目标类中的 `@AutoMapping` 注解,原封不动拷贝到当前类,再修改注解名称即可。 如果还是不理解,这里可以认为,该注解就是本该应用在目标类中的 `@AutoMapping` 注解,原封不动拷贝到当前类,再修改注解名称即可。
## 不可变类型设计
> since 1.3.2
当一个类型是不可变类型时,之前默认的规则,生成的 `T convert(S source, @MappingTarget T target)` 可能会存在问题。
所以,可以使用任意包下的 `Immutable` 注解,标识一个类为不可变类型,
当为不可变类型时,`@MappingTarget` 没有意义,上面的方法最终生成如下:
```java
public T convert(S source, @MappingTarget T target) {
return target;
}
```

View File

@ -23,10 +23,13 @@ public class MapStructPlusConfiguration {
} }
``` ```
:::warning :::info
**当使用该方式配置时强烈建议在编译参数中指定配置类为当前类以解决IDEA部分编译场景时出现的各种问题该功能从 1.3.1 开始支持**
配置时,需要在启动参数中添加 `-Amapstruct.plus.mapperConfigClass` 参数,该参数的值为配置类的全路径名称: 1.3.2 已彻底适配 IDEA 部分编译,无需再添加如下配置。
~~当使用该方式配置时强烈建议在编译参数中指定配置类为当前类以解决IDEA部分编译场景时出现的各种问题该功能从 1.3.1 开始支持
配置时,需要在启动参数中添加 `-Amapstruct.plus.mapperConfigClass` 参数,该参数的值为配置类的全路径名称~~
```xml ```xml
<plugin> <plugin>

View File

@ -6,6 +6,11 @@ category:
description: MapStructPlus release log description: MapStructPlus release log
--- ---
### 1.3.2
- 不可变对象支持,可以使用任意包下的 `Immutable` 标注类型为不可变类
- 全面适配 IDEA 部分编译问题,使用更加流畅丝滑
### 1.3.1 ### 1.3.1
- 增加编译参数中指定配置类的功能 - 增加编译参数中指定配置类的功能

View File

@ -18,7 +18,7 @@
<properties> <properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<mapstruct.version>1.5.1.Final</mapstruct.version> <mapstruct.version>1.5.1.Final</mapstruct.version>
<mapstruct-plus.version>1.3.1</mapstruct-plus.version> <mapstruct-plus.version>1.3.2</mapstruct-plus.version>
<lombok.version>1.18.22</lombok.version> <lombok.version>1.18.22</lombok.version>
</properties> </properties>

View File

@ -20,4 +20,14 @@ public class Goods {
private GoodsTypeEnum type; private GoodsTypeEnum type;
private String f1;
private String f2;
private String f3;
private String f4;
private String f5;
} }

View File

@ -19,4 +19,14 @@ public class GoodsDto {
private int type; private int type;
private String f1;
private String f2;
private String f3;
private String f4;
private String f5;
} }

View File

@ -23,12 +23,6 @@ public abstract class AbstractAdapterMapperGenerator {
public void write(ProcessingEnvironment processingEnv, public void write(ProcessingEnvironment processingEnv,
Collection<AbstractAdapterMethodMetadata> adapterMethods, Collection<AbstractAdapterMethodMetadata> adapterMethods,
String adapterClassName) { String adapterClassName) {
final TypeElement typeElement =
processingEnv.getElementUtils().getTypeElement(adapterPackage() + "." + adapterClassName);
if (typeElement != null) {
System.out.println("adapter class existed");
return;
}
// write Adapter // write Adapter
try (final Writer writer = processingEnv.getFiler() try (final Writer writer = processingEnv.getFiler()
.createSourceFile(adapterPackage() + "." + adapterClassName) .createSourceFile(adapterPackage() + "." + adapterClassName)

View File

@ -1,6 +1,7 @@
package io.github.linpeilie.processor; package io.github.linpeilie.processor;
import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import com.squareup.javapoet.ClassName; import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.TypeName; import com.squareup.javapoet.TypeName;
@ -39,10 +40,14 @@ import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.function.Supplier; import java.util.function.Supplier;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment; import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes; import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedOptions; import javax.annotation.processing.SupportedOptions;
@ -55,7 +60,6 @@ import javax.lang.model.type.MirroredTypeException;
import javax.lang.model.type.MirroredTypesException; import javax.lang.model.type.MirroredTypesException;
import javax.lang.model.type.TypeMirror; import javax.lang.model.type.TypeMirror;
import javax.tools.Diagnostic; import javax.tools.Diagnostic;
import org.apache.commons.lang3.StringUtils;
import org.mapstruct.MappingConstants; import org.mapstruct.MappingConstants;
import static io.github.linpeilie.processor.Constants.AUTO_ENUM_MAPPER_ANNOTATION; import static io.github.linpeilie.processor.Constants.AUTO_ENUM_MAPPER_ANNOTATION;
@ -65,8 +69,18 @@ import static io.github.linpeilie.processor.Constants.AUTO_MAP_MAPPER_ANNOTATION
import static io.github.linpeilie.processor.Constants.COMPONENT_MODEL_CONFIG_ANNOTATION; import static io.github.linpeilie.processor.Constants.COMPONENT_MODEL_CONFIG_ANNOTATION;
import static io.github.linpeilie.processor.Constants.MAPPER_ANNOTATION; import static io.github.linpeilie.processor.Constants.MAPPER_ANNOTATION;
import static io.github.linpeilie.processor.Constants.MAPPER_CONFIG_ANNOTATION; import static io.github.linpeilie.processor.Constants.MAPPER_CONFIG_ANNOTATION;
import static io.github.linpeilie.processor.ProcessorOptions.ADAPTER_CLASS_NAME;
import static io.github.linpeilie.processor.ProcessorOptions.ADAPTER_PACKAGE;
import static io.github.linpeilie.processor.ProcessorOptions.BUILDER_BUILD_METHOD;
import static io.github.linpeilie.processor.ProcessorOptions.BUILDER_DISABLE_BUILDER;
import static io.github.linpeilie.processor.ProcessorOptions.MAPPER_CONFIG_CLASS;
import static io.github.linpeilie.processor.ProcessorOptions.MAPPER_PACKAGE;
import static io.github.linpeilie.processor.ProcessorOptions.MAP_ADAPTER_CLASS_NAME;
import static io.github.linpeilie.processor.ProcessorOptions.NULL_VALUE_MAPPING_STRATEGY;
import static io.github.linpeilie.processor.ProcessorOptions.NULL_VALUE_PROPERTY_MAPPING_STRATEGY;
import static io.github.linpeilie.processor.ProcessorOptions.UNMAPPED_SOURCE_POLICY;
import static io.github.linpeilie.processor.ProcessorOptions.UNMAPPED_TARGET_POLICY;
import static javax.tools.Diagnostic.Kind.ERROR; import static javax.tools.Diagnostic.Kind.ERROR;
import static io.github.linpeilie.processor.ProcessorOptions.*;
@SupportedAnnotationTypes({AUTO_MAPPER_ANNOTATION, AUTO_MAPPERS_ANNOTATION, AUTO_MAP_MAPPER_ANNOTATION, @SupportedAnnotationTypes({AUTO_MAPPER_ANNOTATION, AUTO_MAPPERS_ANNOTATION, AUTO_MAP_MAPPER_ANNOTATION,
AUTO_ENUM_MAPPER_ANNOTATION, MAPPER_CONFIG_ANNOTATION, COMPONENT_MODEL_CONFIG_ANNOTATION, AUTO_ENUM_MAPPER_ANNOTATION, MAPPER_CONFIG_ANNOTATION, COMPONENT_MODEL_CONFIG_ANNOTATION,
@ -104,6 +118,10 @@ public class AutoMapperProcessor extends AbstractProcessor {
private final Set<String> mapperSet = new HashSet<>(); private final Set<String> mapperSet = new HashSet<>();
private Messager messager;
private Filer filer;
public AutoMapperProcessor() { public AutoMapperProcessor() {
this.mapperGenerator = new AutoMapperGenerator(); this.mapperGenerator = new AutoMapperGenerator();
this.mapperConfigGenerator = new MapperConfigGenerator(); this.mapperConfigGenerator = new MapperConfigGenerator();
@ -137,14 +155,32 @@ public class AutoMapperProcessor extends AbstractProcessor {
return COMPONENT_MODEL_CONFIG_ANNOTATION.contentEquals(annotation.getQualifiedName()); return COMPONENT_MODEL_CONFIG_ANNOTATION.contentEquals(annotation.getQualifiedName());
} }
@Override
public synchronized void init(final ProcessingEnvironment processingEnv) {
super.init(processingEnv);
messager = processingEnv.getMessager();
filer = processingEnv.getFiler();
}
@Override @Override
public boolean process(final Set<? extends TypeElement> annotations, final RoundEnvironment roundEnv) { public boolean process(final Set<? extends TypeElement> annotations, final RoundEnvironment roundEnv) {
try {
doProcess(annotations, roundEnv);
} catch (Exception e) {
messager.printMessage(ERROR, ExceptionUtil.stacktraceToString(e));
}
return false;
}
private void doProcess(final Set<? extends TypeElement> annotations, final RoundEnvironment roundEnv) {
boolean hasAutoMapper = annotations.stream().anyMatch(this::isAutoMapperAnnotation); boolean hasAutoMapper = annotations.stream().anyMatch(this::isAutoMapperAnnotation);
final boolean hasAutoMapMapper = annotations.stream().anyMatch(this::isAutoMapMapperAnnotation); final boolean hasAutoMapMapper = annotations.stream().anyMatch(this::isAutoMapMapperAnnotation);
final boolean hasAutoEnumMapper = annotations.stream().anyMatch(this::isAutoEnumMapperAnnotation); final boolean hasAutoEnumMapper = annotations.stream().anyMatch(this::isAutoEnumMapperAnnotation);
final boolean hasAutoMapMappers = annotations.stream().anyMatch(this::isAutoMappersAnnotation); final boolean hasAutoMapMappers = annotations.stream().anyMatch(this::isAutoMappersAnnotation);
if (!hasAutoMapper && !hasAutoMapMapper && !hasAutoEnumMapper && !hasAutoMapMappers) { final boolean hasMapperConfig = annotations.stream().anyMatch(this::isMapperConfigAnnotation);
return false; if (!hasAutoMapper && !hasAutoMapMapper && !hasAutoEnumMapper && !hasAutoMapMappers && !hasMapperConfig) {
return;
} }
// 刷新配置 // 刷新配置
refreshProperties(annotations, roundEnv); refreshProperties(annotations, roundEnv);
@ -162,49 +198,57 @@ public class AutoMapperProcessor extends AbstractProcessor {
} }
// AutoMapMapper // AutoMapMapper
annotations.stream() final TypeElement autoMapMapperAnnotation =
.filter(this::isAutoMapMapperAnnotation) processingEnv.getElementUtils().getTypeElement(AUTO_MAP_MAPPER_ANNOTATION);
.findFirst() processAutoMapMapperAnnotation(roundEnv, autoMapMapperAnnotation);
.ifPresent(annotation -> processAutoMapMapperAnnotation(roundEnv, annotation));
// AutoEnumMapper // AutoEnumMapper
annotations.stream() final TypeElement autoEnumMapperAnnotation =
.filter(this::isAutoEnumMapperAnnotation) processingEnv.getElementUtils().getTypeElement(AUTO_ENUM_MAPPER_ANNOTATION);
.findFirst() processAutoEnumMapperAnnotation(roundEnv, autoEnumMapperAnnotation);
.ifPresent(annotation -> processAutoEnumMapperAnnotation(roundEnv, annotation));
// AutoMapper // AutoMapper
annotations.stream() final TypeElement autoMapperAnnotation = processingEnv.getElementUtils().getTypeElement(AUTO_MAPPER_ANNOTATION);
.filter(this::isAutoMapperAnnotation) processAutoMapperAnnotation(roundEnv, autoMapperAnnotation);
.findFirst()
.ifPresent(annotation -> processAutoMapperAnnotation(roundEnv, annotation));
// AutoMappers // AutoMappers
annotations.stream() final TypeElement autoMappersAnnotation =
.filter(this::isAutoMappersAnnotation) processingEnv.getElementUtils().getTypeElement(AUTO_MAPPERS_ANNOTATION);
.findFirst() processAutoMappersAnnotation(roundEnv, autoMappersAnnotation);
.ifPresent(annotation -> processAutoMappersAnnotation(roundEnv, annotation));
// custom mapper // custom mapper
annotations.stream() final TypeElement mapperAnnotation = processingEnv.getElementUtils().getTypeElement(MAPPER_ANNOTATION);
.filter(this::isMapperAnnotation) processMapperAnnotation(roundEnv, mapperAnnotation);
.findFirst()
.ifPresent(annotation -> processMapperAnnotation(roundEnv, annotation));
// 生成类 // 生成类
generateMapper(); generateMapper();
}
return false; private List<TypeElement> getElementAndMergeHistory(final RoundEnvironment roundEnv,
TypeElement annotation,
BuildCollator buildCollator) {
buildCollator.appendNonexistent(roundEnv.getElementsAnnotatedWith(annotation));
return buildCollator.getRecords();
} }
private void processMapperAnnotation(final RoundEnvironment roundEnv, final TypeElement annotation) { private void processMapperAnnotation(final RoundEnvironment roundEnv, final TypeElement annotation) {
roundEnv.getElementsAnnotatedWith(annotation) if (annotation == null) {
.forEach(element -> customMapperList.add(element.asType())); return;
}
final List<TypeElement> elements = getElementAndMergeHistory(roundEnv, annotation,
new BuildCollator(processingEnv, Constants.MAPPERS_FILE_NAME));
elements.forEach(element -> customMapperList.add(element.asType()));
} }
private void processAutoEnumMapperAnnotation(final RoundEnvironment roundEnv, final TypeElement annotation) { private void processAutoEnumMapperAnnotation(final RoundEnvironment roundEnv, final TypeElement annotation) {
roundEnv.getElementsAnnotatedWith(annotation) if (annotation == null) {
.stream() return;
}
final List<TypeElement> elements = getElementAndMergeHistory(roundEnv, annotation,
new BuildCollator(processingEnv, Constants.ENUM_MAPPERS_FILE_NAME));
elements.stream()
.map(this::buildAutoEnumMapperMetadata) .map(this::buildAutoEnumMapperMetadata)
.filter(Objects::nonNull) .filter(Objects::nonNull)
.forEach(this::writeAutoEnumMapperFile); .forEach(this::writeAutoEnumMapperFile);
@ -225,6 +269,9 @@ public class AutoMapperProcessor extends AbstractProcessor {
} }
private void addAdapterMethodMetadata(final AutoEnumMapperMetadata autoEnumMapperMetadata) { private void addAdapterMethodMetadata(final AutoEnumMapperMetadata autoEnumMapperMetadata) {
if (autoEnumMapperMetadata == null) {
return;
}
// toValue // toValue
final AdapterEnumMethodMetadata toValueProxyMethod = final AdapterEnumMethodMetadata toValueProxyMethod =
new AdapterEnumMethodMetadata(autoEnumMapperMetadata.getSourceClassName(), new AdapterEnumMethodMetadata(autoEnumMapperMetadata.getSourceClassName(),
@ -237,7 +284,7 @@ public class AutoMapperProcessor extends AbstractProcessor {
ClassName.get(autoEnumMapperMetadata.mapperPackage(), autoEnumMapperMetadata.mapperName()), ClassName.get(autoEnumMapperMetadata.mapperPackage(), autoEnumMapperMetadata.mapperName()),
autoEnumMapperMetadata.toEnumMethodName(), autoEnumMapperMetadata.toEnumMethodName(),
autoEnumMapperMetadata.getSourceClassName()); autoEnumMapperMetadata.getSourceClassName());
methodMap.put( methodMap.putIfAbsent(
autoEnumMapperMetadata.getSourceClassName().simpleName() + toValueProxyMethod.getMapperMethodName(), autoEnumMapperMetadata.getSourceClassName().simpleName() + toValueProxyMethod.getMapperMethodName(),
toValueProxyMethod); toValueProxyMethod);
methodMap.put( methodMap.put(
@ -278,13 +325,34 @@ public class AutoMapperProcessor extends AbstractProcessor {
} }
private void processAutoMapMapperAnnotation(final RoundEnvironment roundEnv, final TypeElement annotation) { private void processAutoMapMapperAnnotation(final RoundEnvironment roundEnv, final TypeElement annotation) {
final List<AutoMapperMetadata> autoMapMapperMetadataList = if (annotation == null) {
roundEnv.getElementsAnnotatedWith(annotation).stream().map(ele -> { return;
if (ele.getAnnotation(AutoMapMapper.class) == null) { }
final List<TypeElement> elements = getElementAndMergeHistory(roundEnv, annotation,
new BuildCollator(processingEnv, Constants.AUTO_MAP_MAPPERS_FILE_NAME));
elements.stream()
.map(ele -> buildAutoMapMapperMetadata((TypeElement) ele))
.filter(Objects::nonNull)
.forEach(metadata -> {
this.writeAutoMapperClassFile(metadata);
addAdapterMapMethod(metadata);
});
adapterMapperGenerator.write(processingEnv, mapMethodMap.values(),
AutoMapperProperties.getMapAdapterClassName());
mapperConfigGenerator.write(processingEnv, AutoMapperProperties.getMapConfigClassName(),
AutoMapperProperties.getMapAdapterClassName(), null);
}
private AutoMapperMetadata buildAutoMapMapperMetadata(TypeElement element) {
if (element.getAnnotation(AutoMapMapper.class) == null) {
return null; return null;
} }
ClassName source = ClassName.get("java.util", "Map"); ClassName source = ClassName.get("java.util", "Map");
ClassName target = ClassName.get((TypeElement) ele); ClassName target = ClassName.get(element);
List<ClassName> uses = Arrays.asList(ClassName.get("io.github.linpeilie.map", "MapObjectConvert")); List<ClassName> uses = Arrays.asList(ClassName.get("io.github.linpeilie.map", "MapObjectConvert"));
final AutoMapperMetadata autoMapperMetadata = new AutoMapMapperMetadata(); final AutoMapperMetadata autoMapperMetadata = new AutoMapMapperMetadata();
@ -296,19 +364,16 @@ public class AutoMapperProcessor extends AbstractProcessor {
autoMapperMetadata.setMapstructConfigClass(ClassName.get(AutoMapperProperties.getConfigPackage(), autoMapperMetadata.setMapstructConfigClass(ClassName.get(AutoMapperProperties.getConfigPackage(),
AutoMapperProperties.getMapConfigClassName())); AutoMapperProperties.getMapConfigClassName()));
return autoMapperMetadata; return autoMapperMetadata;
}).filter(Objects::nonNull).collect(Collectors.toList()); }
autoMapMapperMetadataList.forEach(metadata -> {
this.writeAutoMapperClassFile(metadata); private void addAdapterMapMethod(AutoMapperMetadata metadata) {
if (metadata == null) {
return;
}
addAdapterMapMethod(metadata.getSourceClassName(), metadata.getTargetClassName(), metadata.mapperClass(), addAdapterMapMethod(metadata.getSourceClassName(), metadata.getTargetClassName(), metadata.mapperClass(),
false); false);
addAdapterMapMethod(ClassName.get("java.lang", "Object"), metadata.getTargetClassName(), addAdapterMapMethod(ClassName.get("java.lang", "Object"), metadata.getTargetClassName(),
metadata.mapperClass(), true); metadata.mapperClass(), true);
});
adapterMapperGenerator.write(processingEnv, mapMethodMap.values(),
AutoMapperProperties.getMapAdapterClassName());
mapperConfigGenerator.write(processingEnv, AutoMapperProperties.getMapConfigClassName(),
AutoMapperProperties.getMapAdapterClassName(), null);
} }
private void loadMapperConfig(MapperConfig mapperConfig) { private void loadMapperConfig(MapperConfig mapperConfig) {
@ -336,12 +401,30 @@ public class AutoMapperProcessor extends AbstractProcessor {
} }
private void refreshProperties(final Set<? extends TypeElement> annotations, final RoundEnvironment roundEnv) { private void refreshProperties(final Set<? extends TypeElement> annotations, final RoundEnvironment roundEnv) {
final BuildCollator buildCollator = new BuildCollator(processingEnv, Constants.MAPPER_CONFIG_FILE_NAME);
// load previous mapper config
final List<TypeElement> typeElements = buildCollator.getRecords();
if (CollectionUtil.isNotEmpty(typeElements)) {
messager.printMessage(Diagnostic.Kind.NOTE,
"The previous Mapper Config Class was read , class name : " + typeElements.get(0));
loadMapperConfig(typeElements.get(0).getAnnotation(MapperConfig.class));
}
// annotation --> MapperConfig // annotation --> MapperConfig
annotations.stream() final TypeElement mapperConfigAnnotation =
.filter(this::isMapperConfigAnnotation) processingEnv.getElementUtils().getTypeElement(MAPPER_CONFIG_ANNOTATION);
.findFirst() if (mapperConfigAnnotation != null) {
.flatMap(annotation -> roundEnv.getElementsAnnotatedWith(annotation).stream().findFirst()) final Optional<? extends Element> mapperConfigOptional =
.ifPresent(element -> loadMapperConfig(element.getAnnotation(MapperConfig.class))); roundEnv.getElementsAnnotatedWith(mapperConfigAnnotation)
.stream()
.findFirst();
if (mapperConfigOptional.isPresent()) {
loadMapperConfig(mapperConfigOptional.get().getAnnotation(MapperConfig.class));
// record
buildCollator.writeTypeElements(CollectionUtil.newArrayList((TypeElement) mapperConfigOptional.get()));
}
}
// special MapperConfig Class // special MapperConfig Class
final String mapperConfigClass = processingEnv.getOptions().get(MAPPER_CONFIG_CLASS); final String mapperConfigClass = processingEnv.getOptions().get(MAPPER_CONFIG_CLASS);
@ -376,7 +459,13 @@ public class AutoMapperProcessor extends AbstractProcessor {
} }
private void processAutoMapperAnnotation(final RoundEnvironment roundEnv, final TypeElement annotation) { private void processAutoMapperAnnotation(final RoundEnvironment roundEnv, final TypeElement annotation) {
final List<AutoMapperMetadata> autoMapperMetadataList = roundEnv.getElementsAnnotatedWith(annotation) if (annotation == null) {
return;
}
final List<TypeElement> elements = getElementAndMergeHistory(roundEnv, annotation,
new BuildCollator(processingEnv, Constants.AUTO_MAPPER_FILE_NAME));
final List<AutoMapperMetadata> autoMapperMetadataList = elements
.stream() .stream()
.map(this::buildAutoMapperMetadata) .map(this::buildAutoMapperMetadata)
.filter(Objects::nonNull) .filter(Objects::nonNull)
@ -386,7 +475,11 @@ public class AutoMapperProcessor extends AbstractProcessor {
} }
private void processAutoMappersAnnotation(final RoundEnvironment roundEnv, final TypeElement annotation) { private void processAutoMappersAnnotation(final RoundEnvironment roundEnv, final TypeElement annotation) {
final List<AutoMapperMetadata> autoMapperMetadata = roundEnv.getElementsAnnotatedWith(annotation) if (annotation == null) {
return;
}
final List<AutoMapperMetadata> autoMapperMetadata = getElementAndMergeHistory(roundEnv, annotation,
new BuildCollator(processingEnv, Constants.AUTO_MAPPERS_FILE_NAME))
.stream() .stream()
.map(this::buildAutoMapperMetadataByAutoMappers) .map(this::buildAutoMapperMetadataByAutoMappers)
.filter(Objects::nonNull) .filter(Objects::nonNull)
@ -420,7 +513,7 @@ public class AutoMapperProcessor extends AbstractProcessor {
return; return;
} }
this.writeAutoMapperClassFile(metadata); this.writeAutoMapperClassFile(metadata);
addAdapterMethod(metadata.getSourceClassName(), metadata.getTargetClassName(), metadata.mapperClass()); addAdapterMethod(metadata);
}); });
adapterMapperGenerator.write(processingEnv, methodMap.values(), AutoMapperProperties.getAdapterClassName()); adapterMapperGenerator.write(processingEnv, methodMap.values(), AutoMapperProperties.getAdapterClassName());
@ -459,7 +552,7 @@ public class AutoMapperProcessor extends AbstractProcessor {
try (final Writer writer = processingEnv.getFiler() try (final Writer writer = processingEnv.getFiler()
.createSourceFile(mapperPackage + "." + mapperClassName) .createSourceFile(mapperPackage + "." + mapperClassName)
.openWriter()) { .openWriter()) {
mapperGenerator.write(metadata, writer); mapperGenerator.write(metadata, processingEnv, writer);
} catch (IOException e) { } catch (IOException e) {
processingEnv.getMessager() processingEnv.getMessager()
.printMessage(ERROR, .printMessage(ERROR,
@ -467,15 +560,19 @@ public class AutoMapperProcessor extends AbstractProcessor {
} }
} }
private void addAdapterMethod(ClassName source, ClassName target, ClassName mapper) { private void addAdapterMethod(AutoMapperMetadata metadata) {
AdapterMethodMetadata adapterMethodMetadata = AdapterMethodMetadata.newInstance(source, target, mapper); if (metadata == null) {
methodMap.put(adapterMethodMetadata.getMethodName(), adapterMethodMetadata); return;
}
AdapterMethodMetadata adapterMethodMetadata = AdapterMethodMetadata.newInstance(
metadata.getSourceClassName(), metadata.getTargetClassName(), metadata.mapperClass());
methodMap.putIfAbsent(adapterMethodMetadata.getMethodName(), adapterMethodMetadata);
} }
private void addAdapterMapMethod(ClassName source, ClassName target, ClassName mapper, boolean objectConverter) { private void addAdapterMapMethod(ClassName source, ClassName target, ClassName mapper, boolean objectConverter) {
final AdapterMapMethodMetadata adapterMapMethodMetadata = final AdapterMapMethodMetadata adapterMapMethodMetadata =
new AdapterMapMethodMetadata(source, target, mapper, objectConverter); new AdapterMapMethodMetadata(source, target, mapper, objectConverter);
mapMethodMap.put(adapterMapMethodMetadata.getMethodName(), adapterMapMethodMetadata); mapMethodMap.putIfAbsent(adapterMapMethodMetadata.getMethodName(), adapterMapMethodMetadata);
} }
private AutoMapperMetadata initAutoMapperMetadata(ClassName source, ClassName target) { private AutoMapperMetadata initAutoMapperMetadata(ClassName source, ClassName target) {

View File

@ -0,0 +1,104 @@
package io.github.linpeilie.processor;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.io.FileUtil;
import java.io.File;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import javax.annotation.processing.Filer;
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.tools.FileObject;
import javax.tools.StandardLocation;
/**
* 构建校对器
*
* 兼容 IDEA 部分构建的问题
*/
public class BuildCollator {
private final ProcessingEnvironment processingEnv;
private final File collatorFile;
private List<TypeElement> records;
public BuildCollator(ProcessingEnvironment processingEnv, String fileName) {
this.processingEnv = processingEnv;
Filer filer = processingEnv.getFiler();
try {
FileObject fileObject = filer.getResource(StandardLocation.CLASS_OUTPUT, "",
Constants.MAPSTRUCT_PLUS_META_INF + File.separator + fileName);
this.collatorFile = new File(fileObject.getName());
if (collatorFile.exists()) {
records = FileUtil.readUtf8Lines(collatorFile)
.stream()
.map(line -> processingEnv.getElementUtils().getTypeElement(line))
.filter(Objects::nonNull)
.collect(Collectors.toList());
} else {
records = new ArrayList<>();
}
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
public void writeTypeElements(Collection<TypeElement> typeElements) {
final Set<String> names = typeElements.stream()
.map(typeElement -> typeElement.getQualifiedName().toString())
.collect(Collectors.toSet());
write(names);
}
private void write(Collection<String> lines) {
if (CollectionUtil.isEmpty(lines)) {
return;
}
records = lines.stream()
.map(line -> processingEnv.getElementUtils().getTypeElement(line))
.filter(Objects::nonNull)
.collect(Collectors.toList());
FileUtil.mkParentDirs(collatorFile);
FileUtil.writeUtf8Lines(lines, collatorFile);
}
private List<String> loadRecords() {
return records.stream()
.map(ele -> ele.getQualifiedName().toString())
.collect(Collectors.toList());
}
public List<TypeElement> getRecords() {
return records;
}
public void consumeRecords(Consumer<TypeElement> consumer) {
final List<TypeElement> typeElements = getRecords();
if (CollectionUtil.isNotEmpty(typeElements)) {
typeElements.forEach(consumer);
}
}
public void appendNonexistent(Collection<? extends Element> newRecords) {
final List<String> lines = loadRecords();
newRecords.forEach(ele -> {
final String classQualifiedName = ((TypeElement) ele).getQualifiedName().toString();
lines.add(classQualifiedName);
});
write(new HashSet<>(lines));
}
}

View File

@ -1,5 +1,6 @@
package io.github.linpeilie.processor; package io.github.linpeilie.processor;
import java.io.File;
import org.mapstruct.MappingConstants; import org.mapstruct.MappingConstants;
public class Constants { public class Constants {
@ -30,15 +31,22 @@ public class Constants {
public static final String COMPONENT_MODEL_CONFIG_ANNOTATION = "io.github.linpeilie.annotations.ComponentModelConfig"; public static final String COMPONENT_MODEL_CONFIG_ANNOTATION = "io.github.linpeilie.annotations.ComponentModelConfig";
public static final String BASE_MAPPER_PACKAGE = "io.github.linpeilie";
public static final String BASE_MAPPER_CLASS_NAME = "BaseMapper";
public static final String BASE_MAP_MAPPER_CLASS_NAME = "BaseMapMapper";
public static final String MAPSTRUCT_MAPPER_PACKAGE = "org.mapstruct"; public static final String MAPSTRUCT_MAPPER_PACKAGE = "org.mapstruct";
public static final String MAPSTRUCT_MAPPER_CLASS_NAME = "Mapper"; public static final String MAPSTRUCT_MAPPER_CLASS_NAME = "Mapper";
public static final String MAPSTRUCT_PLUS_META_INF = "META-INF" + File.separator + "mps";
public static final String MAPPER_CONFIG_FILE_NAME = "config";
public static final String MAPPERS_FILE_NAME = "mappers";
public static final String AUTO_MAPPER_FILE_NAME = "autoMapper";
public static final String AUTO_MAPPERS_FILE_NAME = "autoMappers";
public static final String AUTO_MAP_MAPPERS_FILE_NAME = "autoMapMappers";
public static final String ENUM_MAPPERS_FILE_NAME = "enumMappers";
} }

View File

@ -1,5 +1,6 @@
package io.github.linpeilie.processor.generator; package io.github.linpeilie.processor.generator;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import com.squareup.javapoet.AnnotationSpec; import com.squareup.javapoet.AnnotationSpec;
import com.squareup.javapoet.ClassName; import com.squareup.javapoet.ClassName;
@ -9,7 +10,7 @@ import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterSpec; import com.squareup.javapoet.ParameterSpec;
import com.squareup.javapoet.ParameterizedTypeName; import com.squareup.javapoet.ParameterizedTypeName;
import com.squareup.javapoet.TypeSpec; import com.squareup.javapoet.TypeSpec;
import io.github.linpeilie.processor.AutoMapperProperties; import io.github.linpeilie.annotations.Immutable;
import io.github.linpeilie.processor.metadata.AutoMapperMetadata; import io.github.linpeilie.processor.metadata.AutoMapperMetadata;
import io.github.linpeilie.processor.metadata.AutoMappingMetadata; import io.github.linpeilie.processor.metadata.AutoMappingMetadata;
import java.io.IOException; import java.io.IOException;
@ -21,17 +22,21 @@ import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Modifier; import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.mapstruct.ReportingPolicy;
import static io.github.linpeilie.processor.Constants.*; import static io.github.linpeilie.processor.Constants.*;
public class AutoMapperGenerator { public class AutoMapperGenerator {
public void write(AutoMapperMetadata metadata, Writer writer) { public static final String CONVERT_METHOD_NAME = "convert";
public void write(AutoMapperMetadata metadata, final ProcessingEnvironment processingEnv, Writer writer) {
try { try {
JavaFile.builder(metadata.mapperPackage(), createTypeSpec(metadata)).build().writeTo(writer); JavaFile.builder(metadata.mapperPackage(), createTypeSpec(processingEnv, metadata)).build().writeTo(writer);
} catch (IOException e) { } catch (IOException e) {
throw new UncheckedIOException(e); throw new UncheckedIOException(e);
} catch (Exception e) { } catch (Exception e) {
@ -39,36 +44,73 @@ public class AutoMapperGenerator {
} }
} }
private TypeSpec createTypeSpec(AutoMapperMetadata metadata) { private TypeSpec createTypeSpec(ProcessingEnvironment processingEnv, AutoMapperMetadata metadata) {
ParameterizedTypeName converterName = ParameterizedTypeName converterName =
ParameterizedTypeName.get(metadata.getSuperClass(), metadata.getSuperGenerics()); ParameterizedTypeName.get(metadata.getSuperClass(), metadata.getSuperGenerics());
final ClassName targetClassName = metadata.getTargetClassName();
TypeSpec.Builder builder = TypeSpec.interfaceBuilder(metadata.mapperName()) TypeSpec.Builder builder = TypeSpec.interfaceBuilder(metadata.mapperName())
.addSuperinterface(converterName) .addSuperinterface(converterName)
.addModifiers(Modifier.PUBLIC) .addModifiers(Modifier.PUBLIC)
.addAnnotation(buildGeneratedMapperAnnotationSpec(metadata)); .addAnnotation(buildGeneratedMapperAnnotationSpec(metadata));
if (metadata.getFieldMappingList() != null && !metadata.getFieldMappingList().isEmpty()) {
final ParameterSpec source = ParameterSpec.builder(metadata.getSourceClassName(), "source").build(); final ParameterSpec source = ParameterSpec.builder(metadata.getSourceClassName(), "source").build();
final ParameterSpec target = ParameterSpec.builder(metadata.getTargetClassName(), "target") final ParameterSpec target = ParameterSpec.builder(targetClassName, "target")
.addAnnotation(AnnotationSpec.builder(ClassName.get("org.mapstruct", "MappingTarget")).build()) .addAnnotation(AnnotationSpec.builder(ClassName.get("org.mapstruct", "MappingTarget")).build())
.build(); .build();
if (metadata.getFieldMappingList() != null && !metadata.getFieldMappingList().isEmpty()) {
builder.addMethod(addConvertMethodSpec(Collections.singletonList(source), metadata.getFieldMappingList(), builder.addMethod(addConvertMethodSpec(Collections.singletonList(source), metadata.getFieldMappingList(),
metadata.getTargetClassName())); targetClassName));
builder.addMethod(addConvertMethodSpec(Arrays.asList(source, target), metadata.getFieldMappingList(),
metadata.getTargetClassName()));
} }
boolean targetIsImmutable = classIsImmutable(processingEnv, targetClassName);
if (targetIsImmutable) {
builder.addMethod(addEmptyConvertMethodForImmutableEntity(source, target, targetClassName));
} else {
builder.addMethod(addConvertMethodSpec(Arrays.asList(source, target), metadata.getFieldMappingList(),
targetClassName));
}
return builder.build(); return builder.build();
} }
private MethodSpec addEmptyConvertMethodForImmutableEntity(ParameterSpec source,
ParameterSpec target,
ClassName targetClassName) {
return MethodSpec.methodBuilder(CONVERT_METHOD_NAME)
.addModifiers(Modifier.PUBLIC, Modifier.DEFAULT)
.addParameter(source)
.addParameter(target)
.returns(targetClassName)
.addCode("return target;")
.build();
}
private boolean classIsImmutable(ProcessingEnvironment processingEnv, ClassName className) {
final TypeElement targetElement = processingEnv.getElementUtils()
.getTypeElement(className.packageName() + "." + className.simpleName());
final List<? extends AnnotationMirror> annotationMirrors = targetElement.getAnnotationMirrors();
for (AnnotationMirror annotationMirror : annotationMirrors) {
if (annotationMirror.getAnnotationType().asElement().getSimpleName().contentEquals("Immutable")) {
return true;
}
}
return false;
}
private MethodSpec addConvertMethodSpec(List<ParameterSpec> parameterSpecs, private MethodSpec addConvertMethodSpec(List<ParameterSpec> parameterSpecs,
List<AutoMappingMetadata> autoMappingMetadataList, List<AutoMappingMetadata> autoMappingMetadataList,
ClassName target) { ClassName target) {
return MethodSpec.methodBuilder("convert") final MethodSpec.Builder methodSpecBuilder = MethodSpec.methodBuilder(CONVERT_METHOD_NAME)
.addParameters(parameterSpecs) .addParameters(parameterSpecs)
.addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT) .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
.addAnnotations(buildMappingAnnotations(autoMappingMetadataList)) .returns(target);
.returns(target) if (CollectionUtil.isNotEmpty(autoMappingMetadataList)) {
.build(); methodSpecBuilder.addAnnotations(buildMappingAnnotations(autoMappingMetadataList));
}
return methodSpecBuilder.build();
} }
private List<AnnotationSpec> buildMappingAnnotations(final List<AutoMappingMetadata> autoMappingMetadataList) { private List<AnnotationSpec> buildMappingAnnotations(final List<AutoMappingMetadata> autoMappingMetadataList) {

View File

@ -20,12 +20,6 @@ import static javax.tools.Diagnostic.Kind.ERROR;
public class MapperConfigGenerator { public class MapperConfigGenerator {
public void write(ProcessingEnvironment processingEnv, String mapstructConfigName, String adapterClassName, List<TypeMirror> uses) { public void write(ProcessingEnvironment processingEnv, String mapstructConfigName, String adapterClassName, List<TypeMirror> uses) {
final TypeElement typeElement =
processingEnv.getElementUtils().getTypeElement(AutoMapperProperties.getConfigPackage() + "." + adapterClassName);
if (typeElement != null) {
System.out.println("mapperConfig class existed");
return;
}
try (final Writer writer = processingEnv.getFiler() try (final Writer writer = processingEnv.getFiler()
.createSourceFile(AutoMapperProperties.getConfigPackage() + "." + mapstructConfigName) .createSourceFile(AutoMapperProperties.getConfigPackage() + "." + mapstructConfigName)
.openWriter()) { .openWriter()) {

View File

@ -2,6 +2,7 @@ package io.github.linpeilie.mapstruct;
import io.github.linpeilie.Converter; import io.github.linpeilie.Converter;
import io.github.linpeilie.ConverterFactory; import io.github.linpeilie.ConverterFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.ComponentScan;
@ -12,11 +13,13 @@ import org.springframework.context.annotation.Configuration;
public class MapstructAutoConfiguration { public class MapstructAutoConfiguration {
@Bean @Bean
@ConditionalOnMissingBean
public ConverterFactory converterFactory(ApplicationContext applicationContext) { public ConverterFactory converterFactory(ApplicationContext applicationContext) {
return new SpringConverterFactory(applicationContext); return new SpringConverterFactory(applicationContext);
} }
@Bean @Bean
@ConditionalOnMissingBean
public Converter converter(ConverterFactory converterFactory) { public Converter converter(ConverterFactory converterFactory) {
return new Converter(converterFactory); return new Converter(converterFactory);
} }

View File

@ -11,11 +11,13 @@ public abstract class AbstractCachedConverterFactory implements ConverterFactory
@Override @Override
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public <S, T> BaseMapper<S, T> getMapper(final Class<S> sourceType, final Class<T> targetType) { public <S, T> BaseMapper<S, T> getMapper(final Class<S> sourceType, final Class<T> targetType) {
final String key = key(sourceType, targetType); final Class<?> source = wrapperClass(sourceType);
final Class<?> target = wrapperClass(targetType);
final String key = key(source, target);
if (mapperMap.containsKey(key)) { if (mapperMap.containsKey(key)) {
return mapperMap.get(key); return mapperMap.get(key);
} }
final BaseMapper mapper = findMapper(sourceType, targetType); final BaseMapper mapper = findMapper(source, target);
if (mapper != null) { if (mapper != null) {
mapperMap.put(key, mapper); mapperMap.put(key, mapper);
return mapper; return mapper;
@ -25,11 +27,12 @@ public abstract class AbstractCachedConverterFactory implements ConverterFactory
@Override @Override
public <S> BaseMapMapper<S> getMapMapper(final Class<S> sourceType) { public <S> BaseMapMapper<S> getMapMapper(final Class<S> sourceType) {
final String key = sourceType.getName(); final Class<?> source = wrapperClass(sourceType);
final String key = source.getName();
if (mapMapperMap.containsKey(key)) { if (mapMapperMap.containsKey(key)) {
return mapMapperMap.get(key); return mapMapperMap.get(key);
} }
final BaseMapMapper mapper = findMapMapper(sourceType); final BaseMapMapper mapper = findMapMapper(source);
if (mapper != null) { if (mapper != null) {
mapMapperMap.put(key, mapper); mapMapperMap.put(key, mapper);
return mapper; return mapper;
@ -37,6 +40,10 @@ public abstract class AbstractCachedConverterFactory implements ConverterFactory
return null; return null;
} }
protected Class<?> wrapperClass(final Class<?> clazz) {
return clazz;
}
protected abstract <S, T> BaseMapper findMapper(final Class<S> source, final Class<T> target); protected abstract <S, T> BaseMapper findMapper(final Class<S> source, final Class<T> target);
protected abstract <S> BaseMapMapper findMapMapper(final Class<?> source); protected abstract <S> BaseMapMapper findMapMapper(final Class<?> source);

View File

@ -1,6 +1,7 @@
package io.github.linpeilie; package io.github.linpeilie;
import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.collection.CollectionUtil;
import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -14,7 +15,7 @@ public interface BaseMapper<S, T> {
default List<T> convert(List<S> sourceList) { default List<T> convert(List<S> sourceList) {
if (CollectionUtil.isEmpty(sourceList)) { if (CollectionUtil.isEmpty(sourceList)) {
return Collections.emptyList(); return new ArrayList<>();
} }
return sourceList.stream().map(this::convert).collect(Collectors.toList()); return sourceList.stream().map(this::convert).collect(Collectors.toList());
} }

View File

@ -1,6 +1,6 @@
package io.github.linpeilie; package io.github.linpeilie;
import java.util.Collections; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
@ -49,7 +49,7 @@ public class Converter {
public <S, T> List<T> convert(List<S> source, Class<T> targetType) { public <S, T> List<T> convert(List<S> source, Class<T> targetType) {
if (source == null || source.size() == 0) { if (source == null || source.size() == 0) {
return Collections.emptyList(); return new ArrayList<>();
} }
return source.stream().map(item -> convert(item, targetType)).collect(Collectors.toList()); return source.stream().map(item -> convert(item, targetType)).collect(Collectors.toList());
} }

View File

@ -0,0 +1,13 @@
package io.github.linpeilie.annotations;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface Immutable {
}

View File

@ -17,7 +17,7 @@
</modules> </modules>
<properties> <properties>
<mapstruct-plus.version>1.3.1</mapstruct-plus.version> <mapstruct-plus.version>1.3.2</mapstruct-plus.version>
<maven.compiler.source>8</maven.compiler.source> <maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target> <maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>