diff --git a/README.md b/README.md index 98718a9..1139880 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,7 @@ public class User { ```xml - 1.3.1 + 1.3.2 diff --git a/docs/README.md b/docs/README.md index 32924a9..eb5c9c2 100644 --- a/docs/README.md +++ b/docs/README.md @@ -56,27 +56,22 @@ copyright: false io.github.linpeilie mapstruct-plus-spring-boot-starter - 1.3.1 + 1.3.2 ``` - 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 部分编译问题,使用更加流畅丝滑 …… diff --git a/docs/guide/class-convert.md b/docs/guide/class-convert.md index e63e758..1bb569d 100644 --- a/docs/guide/class-convert.md +++ b/docs/guide/class-convert.md @@ -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; +} +``` \ No newline at end of file diff --git a/docs/guide/configuration.md b/docs/guide/configuration.md index dd9d126..77e46bb 100644 --- a/docs/guide/configuration.md +++ b/docs/guide/configuration.md @@ -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 diff --git a/docs/release/log.md b/docs/release/log.md index 75e4883..ab64483 100644 --- a/docs/release/log.md +++ b/docs/release/log.md @@ -6,6 +6,11 @@ category: description: MapStructPlus release log --- +### 1.3.2 + +- 不可变对象支持,可以使用任意包下的 `Immutable` 标注类型为不可变类 +- 全面适配 IDEA 部分编译问题,使用更加流畅丝滑 + ### 1.3.1 - 增加编译参数中指定配置类的功能 diff --git a/example/pom.xml b/example/pom.xml index 0c1b64a..78fc488 100644 --- a/example/pom.xml +++ b/example/pom.xml @@ -18,7 +18,7 @@ UTF-8 1.5.1.Final - 1.3.1 + 1.3.2 1.18.22 diff --git a/example/spring-boot-with-lombok/src/main/java/io/github/linpeilie/model/Goods.java b/example/spring-boot-with-lombok/src/main/java/io/github/linpeilie/model/Goods.java index ff94496..c8b85b9 100644 --- a/example/spring-boot-with-lombok/src/main/java/io/github/linpeilie/model/Goods.java +++ b/example/spring-boot-with-lombok/src/main/java/io/github/linpeilie/model/Goods.java @@ -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; + } diff --git a/example/spring-boot-with-lombok/src/main/java/io/github/linpeilie/model/GoodsDto.java b/example/spring-boot-with-lombok/src/main/java/io/github/linpeilie/model/GoodsDto.java index d073bb9..0418d7d 100644 --- a/example/spring-boot-with-lombok/src/main/java/io/github/linpeilie/model/GoodsDto.java +++ b/example/spring-boot-with-lombok/src/main/java/io/github/linpeilie/model/GoodsDto.java @@ -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; + } diff --git a/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/AbstractAdapterMapperGenerator.java b/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/AbstractAdapterMapperGenerator.java index cae3ff7..889ff3e 100644 --- a/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/AbstractAdapterMapperGenerator.java +++ b/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/AbstractAdapterMapperGenerator.java @@ -23,12 +23,6 @@ public abstract class AbstractAdapterMapperGenerator { public void write(ProcessingEnvironment processingEnv, Collection 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) diff --git a/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/AutoMapperProcessor.java b/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/AutoMapperProcessor.java index 4a345c9..2c1ff6b 100644 --- a/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/AutoMapperProcessor.java +++ b/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/AutoMapperProcessor.java @@ -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 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 annotations, final RoundEnvironment roundEnv) { + try { + doProcess(annotations, roundEnv); + } catch (Exception e) { + messager.printMessage(ERROR, ExceptionUtil.stacktraceToString(e)); + } + + return false; + } + + private void doProcess(final Set 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 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 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 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,32 +325,21 @@ public class AutoMapperProcessor extends AbstractProcessor { } private void processAutoMapMapperAnnotation(final RoundEnvironment roundEnv, final TypeElement annotation) { - final List autoMapMapperMetadataList = - roundEnv.getElementsAnnotatedWith(annotation).stream().map(ele -> { - if (ele.getAnnotation(AutoMapMapper.class) == null) { - return null; - } - ClassName source = ClassName.get("java.util", "Map"); - ClassName target = ClassName.get((TypeElement) ele); - List uses = Arrays.asList(ClassName.get("io.github.linpeilie.map", "MapObjectConvert")); + if (annotation == null) { + return; + } + + final List 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); + }); - final AutoMapperMetadata autoMapperMetadata = new AutoMapMapperMetadata(); - autoMapperMetadata.setTargetClassName(target); - autoMapperMetadata.setSourceClassName(source); - autoMapperMetadata.setUsesClassNameList(uses); - autoMapperMetadata.setSuperClass(ClassName.get("io.github.linpeilie", "BaseMapMapper")); - autoMapperMetadata.setSuperGenerics(new ClassName[] {target}); - autoMapperMetadata.setMapstructConfigClass(ClassName.get(AutoMapperProperties.getConfigPackage(), - AutoMapperProperties.getMapConfigClassName())); - return autoMapperMetadata; - }).filter(Objects::nonNull).collect(Collectors.toList()); - autoMapMapperMetadataList.forEach(metadata -> { - this.writeAutoMapperClassFile(metadata); - 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()); @@ -311,6 +347,35 @@ public class AutoMapperProcessor extends AbstractProcessor { 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(element); + List uses = Arrays.asList(ClassName.get("io.github.linpeilie.map", "MapObjectConvert")); + + final AutoMapperMetadata autoMapperMetadata = new AutoMapMapperMetadata(); + autoMapperMetadata.setTargetClassName(target); + autoMapperMetadata.setSourceClassName(source); + autoMapperMetadata.setUsesClassNameList(uses); + autoMapperMetadata.setSuperClass(ClassName.get("io.github.linpeilie", "BaseMapMapper")); + autoMapperMetadata.setSuperGenerics(new ClassName[] {target}); + autoMapperMetadata.setMapstructConfigClass(ClassName.get(AutoMapperProperties.getConfigPackage(), + AutoMapperProperties.getMapConfigClassName())); + return autoMapperMetadata; + } + + 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); + } + private void loadMapperConfig(MapperConfig mapperConfig) { if (mapperConfig == null) { return; @@ -336,12 +401,30 @@ public class AutoMapperProcessor extends AbstractProcessor { } private void refreshProperties(final Set annotations, final RoundEnvironment roundEnv) { + final BuildCollator buildCollator = new BuildCollator(processingEnv, Constants.MAPPER_CONFIG_FILE_NAME); + + // load previous mapper config + final List 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 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 autoMapperMetadataList = roundEnv.getElementsAnnotatedWith(annotation) + if (annotation == null) { + return; + } + final List elements = getElementAndMergeHistory(roundEnv, annotation, + new BuildCollator(processingEnv, Constants.AUTO_MAPPER_FILE_NAME)); + + final List 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 = roundEnv.getElementsAnnotatedWith(annotation) + if (annotation == null) { + return; + } + final List 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) { diff --git a/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/BuildCollator.java b/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/BuildCollator.java new file mode 100644 index 0000000..2754530 --- /dev/null +++ b/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/BuildCollator.java @@ -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 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 typeElements) { + final Set names = typeElements.stream() + .map(typeElement -> typeElement.getQualifiedName().toString()) + .collect(Collectors.toSet()); + write(names); + } + + private void write(Collection 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 loadRecords() { + return records.stream() + .map(ele -> ele.getQualifiedName().toString()) + .collect(Collectors.toList()); + } + + public List getRecords() { + return records; + } + + public void consumeRecords(Consumer consumer) { + final List typeElements = getRecords(); + if (CollectionUtil.isNotEmpty(typeElements)) { + typeElements.forEach(consumer); + } + } + + public void appendNonexistent(Collection newRecords) { + final List lines = loadRecords(); + newRecords.forEach(ele -> { + final String classQualifiedName = ((TypeElement) ele).getQualifiedName().toString(); + lines.add(classQualifiedName); + }); + write(new HashSet<>(lines)); + } + +} diff --git a/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/Constants.java b/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/Constants.java index f45493a..f755211 100644 --- a/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/Constants.java +++ b/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/Constants.java @@ -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"; } diff --git a/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/generator/AutoMapperGenerator.java b/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/generator/AutoMapperGenerator.java index 94783f7..db0768e 100644 --- a/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/generator/AutoMapperGenerator.java +++ b/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/generator/AutoMapperGenerator.java @@ -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)); + + final ParameterSpec source = ParameterSpec.builder(metadata.getSourceClassName(), "source").build(); + final ParameterSpec target = ParameterSpec.builder(targetClassName, "target") + .addAnnotation(AnnotationSpec.builder(ClassName.get("org.mapstruct", "MappingTarget")).build()) + .build(); if (metadata.getFieldMappingList() != null && !metadata.getFieldMappingList().isEmpty()) { - final ParameterSpec source = ParameterSpec.builder(metadata.getSourceClassName(), "source").build(); - final ParameterSpec target = ParameterSpec.builder(metadata.getTargetClassName(), "target") - .addAnnotation(AnnotationSpec.builder(ClassName.get("org.mapstruct", "MappingTarget")).build()) - .build(); 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 annotationMirrors = targetElement.getAnnotationMirrors(); + for (AnnotationMirror annotationMirror : annotationMirrors) { + if (annotationMirror.getAnnotationType().asElement().getSimpleName().contentEquals("Immutable")) { + return true; + } + } + return false; + } + private MethodSpec addConvertMethodSpec(List parameterSpecs, List 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 buildMappingAnnotations(final List autoMappingMetadataList) { diff --git a/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/generator/MapperConfigGenerator.java b/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/generator/MapperConfigGenerator.java index fe192aa..0e086f3 100644 --- a/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/generator/MapperConfigGenerator.java +++ b/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/generator/MapperConfigGenerator.java @@ -20,12 +20,6 @@ import static javax.tools.Diagnostic.Kind.ERROR; public class MapperConfigGenerator { public void write(ProcessingEnvironment processingEnv, String mapstructConfigName, String adapterClassName, List 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()) { diff --git a/mapstruct-plus-spring-boot-starter/src/main/java/io/github/linpeilie/mapstruct/MapstructAutoConfiguration.java b/mapstruct-plus-spring-boot-starter/src/main/java/io/github/linpeilie/mapstruct/MapstructAutoConfiguration.java index 03b7a28..23743f5 100644 --- a/mapstruct-plus-spring-boot-starter/src/main/java/io/github/linpeilie/mapstruct/MapstructAutoConfiguration.java +++ b/mapstruct-plus-spring-boot-starter/src/main/java/io/github/linpeilie/mapstruct/MapstructAutoConfiguration.java @@ -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); } diff --git a/mapstruct-plus/src/main/java/io/github/linpeilie/AbstractCachedConverterFactory.java b/mapstruct-plus/src/main/java/io/github/linpeilie/AbstractCachedConverterFactory.java index 4a23056..84c82dd 100644 --- a/mapstruct-plus/src/main/java/io/github/linpeilie/AbstractCachedConverterFactory.java +++ b/mapstruct-plus/src/main/java/io/github/linpeilie/AbstractCachedConverterFactory.java @@ -11,11 +11,13 @@ public abstract class AbstractCachedConverterFactory implements ConverterFactory @Override @SuppressWarnings("unchecked") public BaseMapper getMapper(final Class sourceType, final Class 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 BaseMapMapper getMapMapper(final Class 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 BaseMapper findMapper(final Class source, final Class target); protected abstract BaseMapMapper findMapMapper(final Class source); diff --git a/mapstruct-plus/src/main/java/io/github/linpeilie/BaseMapper.java b/mapstruct-plus/src/main/java/io/github/linpeilie/BaseMapper.java index c300546..e031b5a 100644 --- a/mapstruct-plus/src/main/java/io/github/linpeilie/BaseMapper.java +++ b/mapstruct-plus/src/main/java/io/github/linpeilie/BaseMapper.java @@ -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 { default List convert(List sourceList) { if (CollectionUtil.isEmpty(sourceList)) { - return Collections.emptyList(); + return new ArrayList<>(); } return sourceList.stream().map(this::convert).collect(Collectors.toList()); } diff --git a/mapstruct-plus/src/main/java/io/github/linpeilie/Converter.java b/mapstruct-plus/src/main/java/io/github/linpeilie/Converter.java index c3c4b0e..459c22e 100644 --- a/mapstruct-plus/src/main/java/io/github/linpeilie/Converter.java +++ b/mapstruct-plus/src/main/java/io/github/linpeilie/Converter.java @@ -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 List convert(List source, Class targetType) { if (source == null || source.size() == 0) { - return Collections.emptyList(); + return new ArrayList<>(); } return source.stream().map(item -> convert(item, targetType)).collect(Collectors.toList()); } diff --git a/mapstruct-plus/src/main/java/io/github/linpeilie/annotations/Immutable.java b/mapstruct-plus/src/main/java/io/github/linpeilie/annotations/Immutable.java new file mode 100644 index 0000000..51bbd76 --- /dev/null +++ b/mapstruct-plus/src/main/java/io/github/linpeilie/annotations/Immutable.java @@ -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 { +} diff --git a/pom.xml b/pom.xml index 50d5ad8..f9b34e3 100644 --- a/pom.xml +++ b/pom.xml @@ -17,7 +17,7 @@ - 1.3.1 + 1.3.2 8 8 UTF-8