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
<properties>
<mapstruct-plus.version>1.3.1</mapstruct-plus.version>
<mapstruct-plus.version>1.3.2</mapstruct-plus.version>
</properties>
<dependencies>
<dependency>

View File

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

View File

@ -514,3 +514,17 @@ public class CarDtoToCarMapperImpl implements CarDtoToCarMapper {
如果还是不理解,这里可以认为,该注解就是本该应用在目标类中的 `@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
**当使用该方式配置时强烈建议在编译参数中指定配置类为当前类以解决IDEA部分编译场景时出现的各种问题该功能从 1.3.1 开始支持**
:::info
配置时,需要在启动参数中添加 `-Amapstruct.plus.mapperConfigClass` 参数,该参数的值为配置类的全路径名称:
1.3.2 已彻底适配 IDEA 部分编译,无需再添加如下配置。
~~当使用该方式配置时强烈建议在编译参数中指定配置类为当前类以解决IDEA部分编译场景时出现的各种问题该功能从 1.3.1 开始支持
配置时,需要在启动参数中添加 `-Amapstruct.plus.mapperConfigClass` 参数,该参数的值为配置类的全路径名称~~
```xml
<plugin>

View File

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

View File

@ -18,7 +18,7 @@
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<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>
</properties>

View File

@ -20,4 +20,14 @@ public class Goods {
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 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,
Collection<AbstractAdapterMethodMetadata> adapterMethods,
String adapterClassName) {
final TypeElement typeElement =
processingEnv.getElementUtils().getTypeElement(adapterPackage() + "." + adapterClassName);
if (typeElement != null) {
System.out.println("adapter class existed");
return;
}
// write Adapter
try (final Writer writer = processingEnv.getFiler()
.createSourceFile(adapterPackage() + "." + adapterClassName)

View File

@ -1,6 +1,7 @@
package io.github.linpeilie.processor;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.util.StrUtil;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.TypeName;
@ -39,10 +40,14 @@ import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.Supplier;
import java.util.stream.Collectors;
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.SupportedAnnotationTypes;
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.TypeMirror;
import javax.tools.Diagnostic;
import org.apache.commons.lang3.StringUtils;
import org.mapstruct.MappingConstants;
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.MAPPER_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 io.github.linpeilie.processor.ProcessorOptions.*;
@SupportedAnnotationTypes({AUTO_MAPPER_ANNOTATION, AUTO_MAPPERS_ANNOTATION, AUTO_MAP_MAPPER_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 Messager messager;
private Filer filer;
public AutoMapperProcessor() {
this.mapperGenerator = new AutoMapperGenerator();
this.mapperConfigGenerator = new MapperConfigGenerator();
@ -137,14 +155,32 @@ public class AutoMapperProcessor extends AbstractProcessor {
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
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);
final boolean hasAutoMapMapper = annotations.stream().anyMatch(this::isAutoMapMapperAnnotation);
final boolean hasAutoEnumMapper = annotations.stream().anyMatch(this::isAutoEnumMapperAnnotation);
final boolean hasAutoMapMappers = annotations.stream().anyMatch(this::isAutoMappersAnnotation);
if (!hasAutoMapper && !hasAutoMapMapper && !hasAutoEnumMapper && !hasAutoMapMappers) {
return false;
final boolean hasMapperConfig = annotations.stream().anyMatch(this::isMapperConfigAnnotation);
if (!hasAutoMapper && !hasAutoMapMapper && !hasAutoEnumMapper && !hasAutoMapMappers && !hasMapperConfig) {
return;
}
// 刷新配置
refreshProperties(annotations, roundEnv);
@ -162,49 +198,57 @@ public class AutoMapperProcessor extends AbstractProcessor {
}
// AutoMapMapper
annotations.stream()
.filter(this::isAutoMapMapperAnnotation)
.findFirst()
.ifPresent(annotation -> processAutoMapMapperAnnotation(roundEnv, annotation));
final TypeElement autoMapMapperAnnotation =
processingEnv.getElementUtils().getTypeElement(AUTO_MAP_MAPPER_ANNOTATION);
processAutoMapMapperAnnotation(roundEnv, autoMapMapperAnnotation);
// AutoEnumMapper
annotations.stream()
.filter(this::isAutoEnumMapperAnnotation)
.findFirst()
.ifPresent(annotation -> processAutoEnumMapperAnnotation(roundEnv, annotation));
final TypeElement autoEnumMapperAnnotation =
processingEnv.getElementUtils().getTypeElement(AUTO_ENUM_MAPPER_ANNOTATION);
processAutoEnumMapperAnnotation(roundEnv, autoEnumMapperAnnotation);
// AutoMapper
annotations.stream()
.filter(this::isAutoMapperAnnotation)
.findFirst()
.ifPresent(annotation -> processAutoMapperAnnotation(roundEnv, annotation));
final TypeElement autoMapperAnnotation = processingEnv.getElementUtils().getTypeElement(AUTO_MAPPER_ANNOTATION);
processAutoMapperAnnotation(roundEnv, autoMapperAnnotation);
// AutoMappers
annotations.stream()
.filter(this::isAutoMappersAnnotation)
.findFirst()
.ifPresent(annotation -> processAutoMappersAnnotation(roundEnv, annotation));
final TypeElement autoMappersAnnotation =
processingEnv.getElementUtils().getTypeElement(AUTO_MAPPERS_ANNOTATION);
processAutoMappersAnnotation(roundEnv, autoMappersAnnotation);
// custom mapper
annotations.stream()
.filter(this::isMapperAnnotation)
.findFirst()
.ifPresent(annotation -> processMapperAnnotation(roundEnv, annotation));
final TypeElement mapperAnnotation = processingEnv.getElementUtils().getTypeElement(MAPPER_ANNOTATION);
processMapperAnnotation(roundEnv, mapperAnnotation);
// 生成类
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) {
roundEnv.getElementsAnnotatedWith(annotation)
.forEach(element -> customMapperList.add(element.asType()));
if (annotation == null) {
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) {
roundEnv.getElementsAnnotatedWith(annotation)
.stream()
if (annotation == null) {
return;
}
final List<TypeElement> elements = getElementAndMergeHistory(roundEnv, annotation,
new BuildCollator(processingEnv, Constants.ENUM_MAPPERS_FILE_NAME));
elements.stream()
.map(this::buildAutoEnumMapperMetadata)
.filter(Objects::nonNull)
.forEach(this::writeAutoEnumMapperFile);
@ -225,6 +269,9 @@ public class AutoMapperProcessor extends AbstractProcessor {
}
private void addAdapterMethodMetadata(final AutoEnumMapperMetadata autoEnumMapperMetadata) {
if (autoEnumMapperMetadata == null) {
return;
}
// toValue
final AdapterEnumMethodMetadata toValueProxyMethod =
new AdapterEnumMethodMetadata(autoEnumMapperMetadata.getSourceClassName(),
@ -237,7 +284,7 @@ public class AutoMapperProcessor extends AbstractProcessor {
ClassName.get(autoEnumMapperMetadata.mapperPackage(), autoEnumMapperMetadata.mapperName()),
autoEnumMapperMetadata.toEnumMethodName(),
autoEnumMapperMetadata.getSourceClassName());
methodMap.put(
methodMap.putIfAbsent(
autoEnumMapperMetadata.getSourceClassName().simpleName() + toValueProxyMethod.getMapperMethodName(),
toValueProxyMethod);
methodMap.put(
@ -278,13 +325,34 @@ public class AutoMapperProcessor extends AbstractProcessor {
}
private void processAutoMapMapperAnnotation(final RoundEnvironment roundEnv, final TypeElement annotation) {
final List<AutoMapperMetadata> autoMapMapperMetadataList =
roundEnv.getElementsAnnotatedWith(annotation).stream().map(ele -> {
if (ele.getAnnotation(AutoMapMapper.class) == null) {
if (annotation == null) {
return;
}
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;
}
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"));
final AutoMapperMetadata autoMapperMetadata = new AutoMapMapperMetadata();
@ -296,19 +364,16 @@ public class AutoMapperProcessor extends AbstractProcessor {
autoMapperMetadata.setMapstructConfigClass(ClassName.get(AutoMapperProperties.getConfigPackage(),
AutoMapperProperties.getMapConfigClassName()));
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(),
false);
addAdapterMapMethod(ClassName.get("java.lang", "Object"), metadata.getTargetClassName(),
metadata.mapperClass(), true);
});
adapterMapperGenerator.write(processingEnv, mapMethodMap.values(),
AutoMapperProperties.getMapAdapterClassName());
mapperConfigGenerator.write(processingEnv, AutoMapperProperties.getMapConfigClassName(),
AutoMapperProperties.getMapAdapterClassName(), null);
}
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) {
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
annotations.stream()
.filter(this::isMapperConfigAnnotation)
.findFirst()
.flatMap(annotation -> roundEnv.getElementsAnnotatedWith(annotation).stream().findFirst())
.ifPresent(element -> loadMapperConfig(element.getAnnotation(MapperConfig.class)));
final TypeElement mapperConfigAnnotation =
processingEnv.getElementUtils().getTypeElement(MAPPER_CONFIG_ANNOTATION);
if (mapperConfigAnnotation != null) {
final Optional<? extends Element> mapperConfigOptional =
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
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) {
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()
.map(this::buildAutoMapperMetadata)
.filter(Objects::nonNull)
@ -386,7 +475,11 @@ public class AutoMapperProcessor extends AbstractProcessor {
}
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()
.map(this::buildAutoMapperMetadataByAutoMappers)
.filter(Objects::nonNull)
@ -420,7 +513,7 @@ public class AutoMapperProcessor extends AbstractProcessor {
return;
}
this.writeAutoMapperClassFile(metadata);
addAdapterMethod(metadata.getSourceClassName(), metadata.getTargetClassName(), metadata.mapperClass());
addAdapterMethod(metadata);
});
adapterMapperGenerator.write(processingEnv, methodMap.values(), AutoMapperProperties.getAdapterClassName());
@ -459,7 +552,7 @@ public class AutoMapperProcessor extends AbstractProcessor {
try (final Writer writer = processingEnv.getFiler()
.createSourceFile(mapperPackage + "." + mapperClassName)
.openWriter()) {
mapperGenerator.write(metadata, writer);
mapperGenerator.write(metadata, processingEnv, writer);
} catch (IOException e) {
processingEnv.getMessager()
.printMessage(ERROR,
@ -467,15 +560,19 @@ public class AutoMapperProcessor extends AbstractProcessor {
}
}
private void addAdapterMethod(ClassName source, ClassName target, ClassName mapper) {
AdapterMethodMetadata adapterMethodMetadata = AdapterMethodMetadata.newInstance(source, target, mapper);
methodMap.put(adapterMethodMetadata.getMethodName(), adapterMethodMetadata);
private void addAdapterMethod(AutoMapperMetadata metadata) {
if (metadata == null) {
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) {
final AdapterMapMethodMetadata adapterMapMethodMetadata =
new AdapterMapMethodMetadata(source, target, mapper, objectConverter);
mapMethodMap.put(adapterMapMethodMetadata.getMethodName(), adapterMapMethodMetadata);
mapMethodMap.putIfAbsent(adapterMapMethodMetadata.getMethodName(), adapterMapMethodMetadata);
}
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;
import java.io.File;
import org.mapstruct.MappingConstants;
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 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_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;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import com.squareup.javapoet.AnnotationSpec;
import com.squareup.javapoet.ClassName;
@ -9,7 +10,7 @@ import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterSpec;
import com.squareup.javapoet.ParameterizedTypeName;
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.AutoMappingMetadata;
import java.io.IOException;
@ -21,17 +22,21 @@ import java.util.Collections;
import java.util.List;
import java.util.Optional;
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.TypeElement;
import org.apache.commons.lang3.StringUtils;
import org.mapstruct.ReportingPolicy;
import static io.github.linpeilie.processor.Constants.*;
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 {
JavaFile.builder(metadata.mapperPackage(), createTypeSpec(metadata)).build().writeTo(writer);
JavaFile.builder(metadata.mapperPackage(), createTypeSpec(processingEnv, metadata)).build().writeTo(writer);
} catch (IOException e) {
throw new UncheckedIOException(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.get(metadata.getSuperClass(), metadata.getSuperGenerics());
final ClassName targetClassName = metadata.getTargetClassName();
TypeSpec.Builder builder = TypeSpec.interfaceBuilder(metadata.mapperName())
.addSuperinterface(converterName)
.addModifiers(Modifier.PUBLIC)
.addAnnotation(buildGeneratedMapperAnnotationSpec(metadata));
if (metadata.getFieldMappingList() != null && !metadata.getFieldMappingList().isEmpty()) {
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())
.build();
if (metadata.getFieldMappingList() != null && !metadata.getFieldMappingList().isEmpty()) {
builder.addMethod(addConvertMethodSpec(Collections.singletonList(source), metadata.getFieldMappingList(),
metadata.getTargetClassName()));
builder.addMethod(addConvertMethodSpec(Arrays.asList(source, target), metadata.getFieldMappingList(),
metadata.getTargetClassName()));
targetClassName));
}
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();
}
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,
List<AutoMappingMetadata> autoMappingMetadataList,
ClassName target) {
return MethodSpec.methodBuilder("convert")
final MethodSpec.Builder methodSpecBuilder = MethodSpec.methodBuilder(CONVERT_METHOD_NAME)
.addParameters(parameterSpecs)
.addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
.addAnnotations(buildMappingAnnotations(autoMappingMetadataList))
.returns(target)
.build();
.returns(target);
if (CollectionUtil.isNotEmpty(autoMappingMetadataList)) {
methodSpecBuilder.addAnnotations(buildMappingAnnotations(autoMappingMetadataList));
}
return methodSpecBuilder.build();
}
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 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()
.createSourceFile(AutoMapperProperties.getConfigPackage() + "." + mapstructConfigName)
.openWriter()) {

View File

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

View File

@ -11,11 +11,13 @@ public abstract class AbstractCachedConverterFactory implements ConverterFactory
@Override
@SuppressWarnings("unchecked")
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)) {
return mapperMap.get(key);
}
final BaseMapper mapper = findMapper(sourceType, targetType);
final BaseMapper mapper = findMapper(source, target);
if (mapper != null) {
mapperMap.put(key, mapper);
return mapper;
@ -25,11 +27,12 @@ public abstract class AbstractCachedConverterFactory implements ConverterFactory
@Override
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)) {
return mapMapperMap.get(key);
}
final BaseMapMapper mapper = findMapMapper(sourceType);
final BaseMapMapper mapper = findMapMapper(source);
if (mapper != null) {
mapMapperMap.put(key, mapper);
return mapper;
@ -37,6 +40,10 @@ public abstract class AbstractCachedConverterFactory implements ConverterFactory
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> BaseMapMapper findMapMapper(final Class<?> source);

View File

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

View File

@ -1,6 +1,6 @@
package io.github.linpeilie;
import java.util.Collections;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@ -49,7 +49,7 @@ public class Converter {
public <S, T> List<T> convert(List<S> source, Class<T> targetType) {
if (source == null || source.size() == 0) {
return Collections.emptyList();
return new ArrayList<>();
}
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>
<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.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>