diff --git a/README.md b/README.md index 41fa2186309f168dd5b95ffe4297e9fc91606b5d..34f15fb44f403c7472ae02deab2094f9c0e0728a 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,135 @@ # mapstruct-plus +## What is MapStruct Plus + +MapStruct Plus is an enhancement to the MapStruct framework. It only does the enhancement, does not make the modification, and can automatically generate the transformation operation between two classes through an annotation, omitting the operation of defining the interface of MapStruct, makes Java type conversion easy and elegant. + +Goal: To be the simplest and most powerful type conversion tool + +**If this project helps you, hope to click a Star to encourage it!** + +## Link + +- [Document](https://mapstruct.plus) + +## Other open source projects + +- **EasyRelation**:[GitHub](https://github.com/linpeilie/easy-relation) | [Gitee](https://gitee.com/easii/easy-relation) | [Document](https://easy-relation.easii.cn) + +## Quick start + + +The following shows how to convert two objects using MapStructPlus. + +Suppose there are two classes, `UserDto` and `User`, representing the data-layer object and business-layer object, respectively: + +- `UserDto` + +```java +public class UserDto { + private String username; + private int age; + private boolean young; + + // getter、setter、toString、equals、hashCode +} +``` + +- `User` + +```java +public class User { + private String username; + private int age; + private boolean young; + + // getter、setter、toString、equals、hashCode +} +``` + +Introducing `mapstruct-plus-spring-boot-starter` dependencies: + +```xml + + latest version + + + + io.github.linpeilie + mapstruct-plus-spring-boot-starter + ${mapstruct-plus.version} + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + 1.8 + 1.8 + + + io.github.linpeilie + mapstruct-plus-processor + ${mapstruct-plus.version} + + + + + + +``` + +Test + +```java +@SpringBootTest +public class QuickStartTest { + + @Autowired + private Converter converter; + + @Test + public void test() { + User user = new User(); + user.setUsername("jack"); + user.setAge(23); + user.setYoung(false); + + UserDto userDto = converter.convert(user, UserDto.class); + System.out.println(userDto); // UserDto{username='jack', age=23, young=false} + + assert user.getUsername().equals(userDto.getUsername()); + assert user.getAge() == userDto.getAge(); + assert user.isYoung() == userDto.isYoung(); + + User newUser = converter.convert(userDto, User.class); + + System.out.println(newUser); // User{username='jack', age=23, young=false} + + assert user.getUsername().equals(newUser.getUsername()); + assert user.getAge() == newUser.getAge(); + assert user.isYoung() == newUser.isYoung(); + } + +} +``` + +## Summary + +With the introduction of dependencies, the steps to using MapStructPlus are very simple. + +1. Add an `AutoMapper` annotation to the class you want to convert +2. Get the `Converter` instance and call the convert method. + +-------- + + ## 这是什么? -Mapstruct Plus 是对 Mapstruct 框架的一个增强,只做增强,不做修改,可以通过一个注解,自动生成两个类之间的转换操作,省略了 Mapstruct 定义接口的操作,使 Java 类型转换更加便捷、优雅。 +MapStruct Plus 是对 MapStruct 框架的一个增强,只做增强,不做修改,可以通过一个注解,自动生成两个类之间的转换操作,省略了 Mapstruct 定义接口的操作,使 Java 类型转换更加便捷、优雅。 目标:做最简单、最强大的类型转换工具 @@ -61,7 +188,7 @@ public class User { ```xml - 1.3.4 + 1.4.0 diff --git a/docs/.vuepress/config.ts b/docs/.vuepress/config.ts index 21710fe08d310cddc3e1566712cd45b77766c1f7..f0a6b10911f8ae81942ee0d2888c84dfda45dc10 100644 --- a/docs/.vuepress/config.ts +++ b/docs/.vuepress/config.ts @@ -17,6 +17,18 @@ export default defineUserConfig({ description: '' } }, + head: [ + ['script', {}, ` + var _hmt = _hmt || []; + (function() { + var hm = document.createElement("script"); + hm.src = "https://hm.baidu.com/hm.js?fab881821b3db8a7c460db1c91ea0f3a"; + var s = document.getElementsByTagName("script")[0]; + s.parentNode.insertBefore(hm, s); + })(); + `], + ['meta', {name: 'baidu-site-verification', content: 'codeva-OceTRzMGJ2'}], + ], theme: recoTheme(themeConfig), markdown: { anchor: { diff --git a/docs/.vuepress/config/zh/series.ts b/docs/.vuepress/config/zh/series.ts index 3fd51109c4c94e713cbf3d0792e66508fffe84c7..1d63d92c2febb3400350c7ab3816701673d7d0f3 100644 --- a/docs/.vuepress/config/zh/series.ts +++ b/docs/.vuepress/config/zh/series.ts @@ -23,6 +23,7 @@ export const series = { { text: 'Map 转对象', link: '/guide/map-to-class' }, { text: '枚举转换', link: '/guide/enum-convert' }, { text: '一个类与多个类之间转换', link: '/guide/multiple-class-convert' }, + { text: '类循环嵌套', link: '/guide/cycle-avoiding' }, { text: '类转换API', link: '/guide/converter-api' }, { text: '配置项', link: '/guide/configuration' }, { text: '常见问题', link: '/guide/faq' }, diff --git a/docs/README.md b/docs/README.md index 76821971095e6d1785e23125f18ed9976e542e47..41b8007c11b5f398862e0111a10aca7d33ff0515 100644 --- a/docs/README.md +++ b/docs/README.md @@ -62,18 +62,26 @@ footer: io.github.linpeilie mapstruct-plus-spring-boot-starter - 1.3.6 + 1.4.0-R1 ``` - gradle ```groovy -implementation group: 'io.github.linpeilie', name: 'mapstruct-plus-spring-boot-starter', version: '1.3.6' +implementation group: 'io.github.linpeilie', name: 'mapstruct-plus-spring-boot-starter', version: '1.4.0-R1' ``` ## 更新日志 +### 1.4.0 + +- **优化复杂对象转换逻辑,占用元空间更小!性能更快!** +- 去除 hutool 等依赖,目前项目中只依赖了 MapStruct +- 适配对象循环嵌套场景 +- [feature#63](https://github.com/linpeilie/mapstruct-plus/pull/63)`AutoMapping`、`ReverseAutoMapping` 支持 `qualifiedByName`、`conditionQualifiedByName` 和 `dependsOn` 属性 +- [issue#I93Z2Z](https://gitee.com/easii/mapstruct-plus/issues/I93Z2Z)`AutoMappings` 支持配置在方法上面 + ### 1.3.6 - 兼容内部类转换 diff --git a/docs/en/README.md b/docs/en/README.md index f2a03a2109a2375085044f08b063b909331452bb..403c0b273f3d1c25528b60da3d99b5cbaccfae25 100644 --- a/docs/en/README.md +++ b/docs/en/README.md @@ -58,18 +58,26 @@ fotter: io.github.linpeilie mapstruct-plus-spring-boot-starter - 1.3.6 + 1.4.0-R1 ``` - gradle ```groovy -implementation group: 'io.github.linpeilie', name: 'mapstruct-plus-spring-boot-starter', version: '1.3.6' +implementation group: 'io.github.linpeilie', name: 'mapstruct-plus-spring-boot-starter', version: '1.4.0-R1' ``` ## Change Log +### 1.4.0 + +- **Optimize complex object conversion logic, take up less meta-space! and faster!** +- Get rid of dependencies such as hutool, which currently only rely on MapStruct in the project. +- The adaptation object loop nesting scenario +- [feature#63](https://github.com/linpeilie/mapstruct-plus/pull/63) `AutoMapping`、`ReverseAutoMapping` supports `qualifiedByName`,`conditionQualifiedByName`,and `dependsOn` properties. +- [issue#I93Z2Z](https://gitee.com/easii/mapstruct-plus/issues/I93Z2Z) `AutoMappings` supports configuration on methods. + ### 1.3.6 - Compatible with internal class conversion. diff --git a/docs/en/release/log.md b/docs/en/release/log.md index 8b97ba68eaf389d021b3e582f9cc742a256e515c..16e2c73c88907306c6d80e7e9d79a0924f81d52d 100644 --- a/docs/en/release/log.md +++ b/docs/en/release/log.md @@ -6,6 +6,14 @@ category: description: MapStructPlus release log --- +### 1.4.0 + +- **Optimize complex object conversion logic, take up less meta-space! and faster!** +- Get rid of dependencies such as hutool, which currently only rely on MapStruct in the project. +- The adaptation object loop nesting scenario +- [feature#63](https://github.com/linpeilie/mapstruct-plus/pull/63) `AutoMapping`、`ReverseAutoMapping` supports `qualifiedByName`,`conditionQualifiedByName`,and `dependsOn` properties. +- [issue#I93Z2Z](https://gitee.com/easii/mapstruct-plus/issues/I93Z2Z) `AutoMappings` supports configuration on methods. + ### 1.3.6 - Compatible with internal class conversion. diff --git a/docs/guide/cycle-avoiding.md b/docs/guide/cycle-avoiding.md new file mode 100644 index 0000000000000000000000000000000000000000..23d8c63604bb852f4a9e526bad1a12e715fe22a5 --- /dev/null +++ b/docs/guide/cycle-avoiding.md @@ -0,0 +1,82 @@ +--- +title: 类循环嵌套 +order: 7 +category: +- 指南 +description: MapStructPlus MapStructPlus类循环嵌套 CycleAvoiding +--- + +## 背景 + +类循环嵌套是指两个类互相引用,例如,源对象和目标对象结构都包含父对象和子对象之间的双向关联。 +当存在这种情况时,直接进行转换时,会导致栈溢出的问题(stack overflow error)。 + +示例: + +```java +@Data +public class TreeNode { + private TreeNode parent; + private List children; +} + +@Data +public class TreeNodeDto { + private TreeNodeDto parent; + private List children; +} +``` + +`parent` 属性可以是其他类型的,可能跨越一个更长的属性链形成的嵌套循环。 + +为了适配这种情况,MapStructPlus 的 **`AutoMapper`** 注解中增加了 **`cycleAvoiding`** 属性,该属性用于标识,是否需要避免循环嵌套的问题。 +默认为 `false`,如果需要避免循环嵌套,需要将该属性设置为 `true`。 + +当配置为 `true` 时,在整个对象的转换过程链路中,会传递一个 `CycleAvoidingMappingContext` 对象,临时保存转换生成的对象, +在转换链路中,如果发现需要生成的对象已经存在,会直接返回该类型,从而避免栈溢出问题。 +所以,配置该属性为 `true` 时,会有一点的性能消耗,如果没有循环嵌套的情况,使用默认配置即可,避免不必要的性能消耗。 + +## 使用示例 + +以上面的示例为例,在 `AutoMapper` 注解中,配置 `cycleAvoiding` 属性为 `true`,如下所示: + +```java +@Data +@AutoMapper(target = TreeNodeDto.class, cycleAvoiding = true) +public class TreeNode { + private TreeNode parent; + private List children; +} + +@Data +@AutoMapper(target = TreeNode.class, cycleAvoiding = true) +public class TreeNodeDto { + private TreeNodeDto parent; + private List children; +} +``` + +编译生成的转换逻辑如下: + +```java +public TreeNodeDto convert(TreeNode arg0, CycleAvoidingMappingContext arg1) { + TreeNodeDto target = arg1.getMappedInstance(arg0, TreeNodeDto.class); + if (target != null) { + return target; + } + + if (arg0 == null) { + return null; + } + + TreeNodeDto treeNodeDto = new TreeNodeDto(); + + arg1.storeMappedInstance(arg0, treeNodeDto); + + treeNodeDto.setParent(demoConvertMapperAdapterForCycleAvoiding.iglm_TreeNodeToTreeNodeDto(arg0.getParent(), arg1)); + treeNodeDto.setChildren( + demoConvertMapperAdapterForCycleAvoiding.iglm_TreeNodeToTreeNodeDto(arg0.getChildren(), arg1)); + + return treeNodeDto; +} +``` \ No newline at end of file diff --git a/docs/guide/faq.md b/docs/guide/faq.md index 41b257c4478cb8e4697e5cb02ca04be0853985fe..f08f39e614fefb0ead9683c11ff7e7019945739c 100644 --- a/docs/guide/faq.md +++ b/docs/guide/faq.md @@ -1,6 +1,6 @@ --- title: 常见问题 -order: 7 +order: 8 category: - 指南 description: MapStructPlus MapStructPlus常见问题 faq diff --git a/docs/guide/map-to-class.md b/docs/guide/map-to-class.md index 6a80b3adb0adbf244a8746e4f0c757a4e0afec9b..7bb1dfaca193c04b7761d3f01982f358574715e2 100644 --- a/docs/guide/map-to-class.md +++ b/docs/guide/map-to-class.md @@ -8,8 +8,26 @@ description: MapStructPlus Map转为对象 map convert to class MapStructPlus 提供了更加强大的 `Map` 转对象的功能。 +::: warning +MapStructPlus 1.4.0 及以后版本,不再内置 [Hutool](https://hutool.cn) 框架,如果需要用到该功能时,需要额外引入 `hutool-core` 依赖。 +::: + ## 使用 +### 添加依赖 + +> 1.4.0 及以后的版本需要添加该依赖,1.4.0之前的版本内置 hutool,不需要额外添加。 + +```xml + + cn.hutool + hutool-core + ${hutool.version} + +``` + +### 添加注解 + **当想要自动生成 `Map` 转为目标类的接口及实现类时,只需要在目标类上添加 `@AutoMapMapper` 注解**。 ## 支持的 value 类型 diff --git a/docs/release/log.md b/docs/release/log.md index 175f8fe3e3fb74153d713f4979e46cfc64641bcc..cf7dc5d6517be26006af87976682b8f2949f6fe0 100644 --- a/docs/release/log.md +++ b/docs/release/log.md @@ -6,6 +6,14 @@ category: description: MapStructPlus release log --- +### 1.4.0 + +- **优化复杂对象转换逻辑,占用元空间更小!性能更快!** +- 去除 hutool 等依赖,目前项目中只依赖了 MapStruct +- 适配对象循环嵌套场景 +- [feature#63](https://github.com/linpeilie/mapstruct-plus/pull/63)`AutoMapping`、`ReverseAutoMapping` 支持 `qualifiedByName`、`conditionQualifiedByName` 和 `dependsOn` 属性 +- [issue#I93Z2Z](https://gitee.com/easii/mapstruct-plus/issues/I93Z2Z)`AutoMappings` 支持配置在方法上面 + ### 1.3.6 - 兼容内部类转换 diff --git a/example/pom.xml b/example/pom.xml index 0a890806ef13e86dcff0984b546d69f082fcb73a..a16c8ff7abdd7e6f26fd9891ee07422b5e50670c 100644 --- a/example/pom.xml +++ b/example/pom.xml @@ -18,8 +18,9 @@ UTF-8 1.5.1.Final - 1.3.6 + 1.4.0-R1 1.18.22 + 5.8.26 @@ -44,6 +45,11 @@ lombok ${lombok.version} + + cn.hutool + hutool-all + ${hutool.version} + diff --git a/example/quick-start/pom.xml b/example/quick-start/pom.xml index 323f2a05cd8596d99a1e093252673a614de4e32b..b3f6f4c7131bf2058879aa8d4625b8bda8ff44f1 100644 --- a/example/quick-start/pom.xml +++ b/example/quick-start/pom.xml @@ -28,6 +28,10 @@ 5.9.2 test + + cn.hutool + hutool-all + diff --git a/example/spring-boot-with-lombok/pom.xml b/example/spring-boot-with-lombok/pom.xml index 2ef8f86031347955cf1fe8fc4a1bfc09a25039fd..81a92c8c0435d671e2e185371f4e50fd8a214baf 100644 --- a/example/spring-boot-with-lombok/pom.xml +++ b/example/spring-boot-with-lombok/pom.xml @@ -46,6 +46,10 @@ org.projectlombok lombok + + cn.hutool + hutool-all + diff --git a/example/spring-boot-with-lombok/src/main/java/io/github/linpeilie/mapper/Titles.java b/example/spring-boot-with-lombok/src/main/java/io/github/linpeilie/mapper/Titles.java new file mode 100644 index 0000000000000000000000000000000000000000..a30b35d9ad8246414531b49dfa67d822ea3c5908 --- /dev/null +++ b/example/spring-boot-with-lombok/src/main/java/io/github/linpeilie/mapper/Titles.java @@ -0,0 +1,27 @@ +package io.github.linpeilie.mapper; + +import org.mapstruct.Named; +import org.springframework.boot.context.properties.bind.Name; +import org.springframework.stereotype.Component; + +@Component +@Named("TitleTranslator") +public class Titles { + + @Named("EnglishToFrench") + public String translateTitleEF(String title) { + if ("One Hundred Years of Solitude".equals(title)) { + return "Cent ans de solitude"; + } + return "Inconnu et inconnu"; + } + + @Named("FrenchToEnglish") + public String translateTitleFE(String title) { + if ("Cent ans de solitude".equals(title)) { + return "One Hundred Years of Solitude"; + } + return "Unknown"; + } + +} diff --git a/example/spring-boot-with-lombok/src/main/java/io/github/linpeilie/mapper/TreeNodeAwareMapper.java b/example/spring-boot-with-lombok/src/main/java/io/github/linpeilie/mapper/TreeNodeAwareMapper.java new file mode 100644 index 0000000000000000000000000000000000000000..5beab407da3e28d9d411bf9d00e25f303661e937 --- /dev/null +++ b/example/spring-boot-with-lombok/src/main/java/io/github/linpeilie/mapper/TreeNodeAwareMapper.java @@ -0,0 +1,19 @@ +package io.github.linpeilie.mapper; + +import io.github.linpeilie.model.TreeNode; +import java.util.List; +import org.mapstruct.Condition; +import org.mapstruct.Named; +import org.springframework.boot.context.properties.bind.Name; +import org.springframework.stereotype.Component; + +@Component +public class TreeNodeAwareMapper { + + @Condition + @Named("limitLeastTwo") + public boolean limitLeastTwo(List treeNodeList) { + return treeNodeList != null && treeNodeList.size() >= 2; + } + +} diff --git a/example/spring-boot-with-lombok/src/main/java/io/github/linpeilie/model/Dept.java b/example/spring-boot-with-lombok/src/main/java/io/github/linpeilie/model/Dept.java new file mode 100644 index 0000000000000000000000000000000000000000..2ee6bd19b753f8512cd53126cded9944d43aaf37 --- /dev/null +++ b/example/spring-boot-with-lombok/src/main/java/io/github/linpeilie/model/Dept.java @@ -0,0 +1,19 @@ +package io.github.linpeilie.model; + +import io.github.linpeilie.annotations.AutoMapper; +import java.util.Set; +import lombok.Data; + +@Data +@AutoMapper(target = DeptDTO.class) +public class Dept { + + private String code; + + private String name; + + private Long parentId; + + private Set children; + +} diff --git a/example/spring-boot-with-lombok/src/main/java/io/github/linpeilie/model/DeptDTO.java b/example/spring-boot-with-lombok/src/main/java/io/github/linpeilie/model/DeptDTO.java new file mode 100644 index 0000000000000000000000000000000000000000..9d59db5ef28deba5fd211eaa9b77c1a9b4f372f6 --- /dev/null +++ b/example/spring-boot-with-lombok/src/main/java/io/github/linpeilie/model/DeptDTO.java @@ -0,0 +1,17 @@ +package io.github.linpeilie.model; + +import io.github.linpeilie.annotations.AutoMapper; +import java.util.Set; +import lombok.Data; + +@Data +@AutoMapper(target = Dept.class) +public class DeptDTO { + + private Long id; + + private String name; + + private Set children; + +} diff --git a/example/spring-boot-with-lombok/src/main/java/io/github/linpeilie/model/Employee.java b/example/spring-boot-with-lombok/src/main/java/io/github/linpeilie/model/Employee.java new file mode 100644 index 0000000000000000000000000000000000000000..804bf0876599ae5adddf3166606f2a87975cbe0d --- /dev/null +++ b/example/spring-boot-with-lombok/src/main/java/io/github/linpeilie/model/Employee.java @@ -0,0 +1,15 @@ +package io.github.linpeilie.model; + +import io.github.linpeilie.annotations.AutoMapper; +import java.util.List; +import lombok.Data; + +@Data +@AutoMapper(target = EmployeeDto.class, cycleAvoiding = true) +public class Employee { + + private String name; + private Employee reportsTo; + private List team; + +} diff --git a/example/spring-boot-with-lombok/src/main/java/io/github/linpeilie/model/Employee1.java b/example/spring-boot-with-lombok/src/main/java/io/github/linpeilie/model/Employee1.java new file mode 100644 index 0000000000000000000000000000000000000000..a19a772bf2c60b6770d3e693de39741c8c00ee8c --- /dev/null +++ b/example/spring-boot-with-lombok/src/main/java/io/github/linpeilie/model/Employee1.java @@ -0,0 +1,12 @@ +package io.github.linpeilie.model; + +import io.github.linpeilie.annotations.AutoMapper; +import lombok.Data; + +@Data +@AutoMapper(target = Employee1Dto.class) +public class Employee1 { + + private Employee employee; + +} diff --git a/example/spring-boot-with-lombok/src/main/java/io/github/linpeilie/model/Employee1Dto.java b/example/spring-boot-with-lombok/src/main/java/io/github/linpeilie/model/Employee1Dto.java new file mode 100644 index 0000000000000000000000000000000000000000..98db85ae3c20f6610bab8e42f5005009f2a70225 --- /dev/null +++ b/example/spring-boot-with-lombok/src/main/java/io/github/linpeilie/model/Employee1Dto.java @@ -0,0 +1,12 @@ +package io.github.linpeilie.model; + +import io.github.linpeilie.annotations.AutoMapper; +import lombok.Data; + +@Data +@AutoMapper(target = Employee1.class) +public class Employee1Dto { + + private EmployeeDto employee; + +} diff --git a/example/spring-boot-with-lombok/src/main/java/io/github/linpeilie/model/EmployeeDto.java b/example/spring-boot-with-lombok/src/main/java/io/github/linpeilie/model/EmployeeDto.java new file mode 100644 index 0000000000000000000000000000000000000000..1bf2bafc7ce3f82fe2e7528ae0ec57e97b326cc0 --- /dev/null +++ b/example/spring-boot-with-lombok/src/main/java/io/github/linpeilie/model/EmployeeDto.java @@ -0,0 +1,13 @@ +package io.github.linpeilie.model; + +import java.util.List; +import lombok.Data; + +@Data +public class EmployeeDto { + + private String employeeName; + private EmployeeDto reportsTo; + private List team; + +} diff --git a/example/spring-boot-with-lombok/src/main/java/io/github/linpeilie/model/EnglishRelease.java b/example/spring-boot-with-lombok/src/main/java/io/github/linpeilie/model/EnglishRelease.java new file mode 100644 index 0000000000000000000000000000000000000000..87f508245c814aee018457fc58775b5b61daa931 --- /dev/null +++ b/example/spring-boot-with-lombok/src/main/java/io/github/linpeilie/model/EnglishRelease.java @@ -0,0 +1,15 @@ +package io.github.linpeilie.model; + +import io.github.linpeilie.annotations.AutoMapper; +import io.github.linpeilie.annotations.AutoMapping; +import io.github.linpeilie.mapper.Titles; +import lombok.Data; + +@Data +@AutoMapper(target = FrenchRelease.class, uses = Titles.class) +public class EnglishRelease { + + @AutoMapping(qualifiedByName = "EnglishToFrench") + private String title; + +} diff --git a/example/spring-boot-with-lombok/src/main/java/io/github/linpeilie/model/FrenchRelease.java b/example/spring-boot-with-lombok/src/main/java/io/github/linpeilie/model/FrenchRelease.java new file mode 100644 index 0000000000000000000000000000000000000000..fdbd4e3284b4dc699fb179da363e653e9ad162b5 --- /dev/null +++ b/example/spring-boot-with-lombok/src/main/java/io/github/linpeilie/model/FrenchRelease.java @@ -0,0 +1,15 @@ +package io.github.linpeilie.model; + +import io.github.linpeilie.annotations.AutoMapper; +import io.github.linpeilie.annotations.AutoMapping; +import io.github.linpeilie.mapper.Titles; +import lombok.Data; + +@Data +@AutoMapper(target = EnglishRelease.class, uses = Titles.class) +public class FrenchRelease { + + @AutoMapping(qualifiedByName = "FrenchToEnglish") + private String title; + +} diff --git a/example/spring-boot-with-lombok/src/main/java/io/github/linpeilie/model/Product.java b/example/spring-boot-with-lombok/src/main/java/io/github/linpeilie/model/Product.java new file mode 100644 index 0000000000000000000000000000000000000000..2c92172cb08b3ab10113ae45cc128727817caea6 --- /dev/null +++ b/example/spring-boot-with-lombok/src/main/java/io/github/linpeilie/model/Product.java @@ -0,0 +1,22 @@ +package io.github.linpeilie.model; + +import io.github.linpeilie.annotations.AutoMapper; +import io.github.linpeilie.annotations.AutoMapping; +import java.util.List; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +@Data +@AutoMapper(target = ProductDto.class, cycleAvoiding = true) +public class Product { + + private Long id; + + private String name; + + @ToString.Exclude + @EqualsAndHashCode.Exclude + private List productPropertyList; + +} diff --git a/example/spring-boot-with-lombok/src/main/java/io/github/linpeilie/model/ProductDto.java b/example/spring-boot-with-lombok/src/main/java/io/github/linpeilie/model/ProductDto.java new file mode 100644 index 0000000000000000000000000000000000000000..d3d238f7388380c866428ae0a35d46d2de922425 --- /dev/null +++ b/example/spring-boot-with-lombok/src/main/java/io/github/linpeilie/model/ProductDto.java @@ -0,0 +1,21 @@ +package io.github.linpeilie.model; + +import io.github.linpeilie.annotations.AutoMapper; +import java.util.List; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +@Data +@AutoMapper(target = Product.class, cycleAvoiding = true) +public class ProductDto { + + private Long id; + + private String name; + + @ToString.Exclude + @EqualsAndHashCode.Exclude + private List productPropertyList; + +} diff --git a/example/spring-boot-with-lombok/src/main/java/io/github/linpeilie/model/ProductProperty.java b/example/spring-boot-with-lombok/src/main/java/io/github/linpeilie/model/ProductProperty.java new file mode 100644 index 0000000000000000000000000000000000000000..1c953499fd9275c7343859872c9a1c4066413a84 --- /dev/null +++ b/example/spring-boot-with-lombok/src/main/java/io/github/linpeilie/model/ProductProperty.java @@ -0,0 +1,20 @@ +package io.github.linpeilie.model; + +import io.github.linpeilie.annotations.AutoMapper; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +@Data +@AutoMapper(target = ProductPropertyDto.class, cycleAvoiding = true) +public class ProductProperty { + + private Long id; + + private String name; + + @ToString.Exclude + @EqualsAndHashCode.Exclude + private Product product; + +} diff --git a/example/spring-boot-with-lombok/src/main/java/io/github/linpeilie/model/ProductPropertyDto.java b/example/spring-boot-with-lombok/src/main/java/io/github/linpeilie/model/ProductPropertyDto.java new file mode 100644 index 0000000000000000000000000000000000000000..1797d31ccc3aacee6c13dc59ba3d50dfdf66eb61 --- /dev/null +++ b/example/spring-boot-with-lombok/src/main/java/io/github/linpeilie/model/ProductPropertyDto.java @@ -0,0 +1,20 @@ +package io.github.linpeilie.model; + +import io.github.linpeilie.annotations.AutoMapper; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +@Data +@AutoMapper(target = ProductProperty.class, cycleAvoiding = true) +public class ProductPropertyDto { + + private Long id; + + private String name; + + @ToString.Exclude + @EqualsAndHashCode.Exclude + private ProductDto product; + +} diff --git a/example/spring-boot-with-lombok/src/main/java/io/github/linpeilie/model/TreeNode.java b/example/spring-boot-with-lombok/src/main/java/io/github/linpeilie/model/TreeNode.java new file mode 100644 index 0000000000000000000000000000000000000000..71a21404b9ef735b3d0b7f493458a8657c679226 --- /dev/null +++ b/example/spring-boot-with-lombok/src/main/java/io/github/linpeilie/model/TreeNode.java @@ -0,0 +1,18 @@ +package io.github.linpeilie.model; + +import io.github.linpeilie.annotations.AutoMapper; +import io.github.linpeilie.annotations.AutoMapping; +import io.github.linpeilie.mapper.TreeNodeAwareMapper; +import java.util.List; +import lombok.Data; + +@Data +@AutoMapper(target = TreeNodeDto.class, cycleAvoiding = true, uses = TreeNodeAwareMapper.class) +public class TreeNode { + + private TreeNode parent; + + @AutoMapping(conditionQualifiedByName = "limitLeastTwo") + private List children; + +} diff --git a/example/spring-boot-with-lombok/src/main/java/io/github/linpeilie/model/TreeNodeDto.java b/example/spring-boot-with-lombok/src/main/java/io/github/linpeilie/model/TreeNodeDto.java new file mode 100644 index 0000000000000000000000000000000000000000..72fe7523918314630a31703b6d91ede9910dbc6c --- /dev/null +++ b/example/spring-boot-with-lombok/src/main/java/io/github/linpeilie/model/TreeNodeDto.java @@ -0,0 +1,15 @@ +package io.github.linpeilie.model; + +import io.github.linpeilie.annotations.AutoMapper; +import java.util.List; +import lombok.Data; + +@Data +@AutoMapper(target = TreeNode.class, cycleAvoiding = true) +public class TreeNodeDto { + + private TreeNodeDto parent; + + private List children; + +} diff --git a/example/spring-boot-with-lombok/src/test/java/io/github/linpeilie/EmployeeMapperTest.java b/example/spring-boot-with-lombok/src/test/java/io/github/linpeilie/EmployeeMapperTest.java new file mode 100644 index 0000000000000000000000000000000000000000..2d9ca14282ce67e62fad098fb075742bea97695e --- /dev/null +++ b/example/spring-boot-with-lombok/src/test/java/io/github/linpeilie/EmployeeMapperTest.java @@ -0,0 +1,69 @@ +package io.github.linpeilie; + +import io.github.linpeilie.model.Employee; +import io.github.linpeilie.model.EmployeeDto; +import java.util.Arrays; +import java.util.List; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import static org.assertj.core.api.Assertions.assertThat; + +@SpringBootTest +public class EmployeeMapperTest { + + @Autowired + private Converter converter; + + @Test + public void testMapDtoToEntity() { + + EmployeeDto teamLeader = employeeDto("Group Leader", null); + + EmployeeDto member1 = employeeDto("Member2", teamLeader); + EmployeeDto member2 = employeeDto("Member2", teamLeader); + teamLeader.setTeam(Arrays.asList(member1, member2)); + + Employee teamLead = converter.convert(teamLeader, Employee.class); + + assertThat(teamLead).isNotNull(); + assertThat(teamLead.getReportsTo()).isNull(); + List team = teamLead.getTeam(); + assertThat(team).hasSize(2); + assertThat(team).extracting("reportsTo").containsExactly(teamLead, teamLead); + } + + private EmployeeDto employeeDto(String name, EmployeeDto reportsTo) { + EmployeeDto employeeDto = new EmployeeDto(); + employeeDto.setEmployeeName(name); + employeeDto.setReportsTo(reportsTo); + return employeeDto; + } + + @Test + public void testMapEntityToDto() { + + Employee teamLeader = employee("Group Leader", null); + + Employee member1 = employee("Member2", teamLeader); + Employee member2 = employee("Member2", teamLeader); + teamLeader.setTeam(Arrays.asList(member1, member2)); + + EmployeeDto teamLead = converter.convert(teamLeader, EmployeeDto.class); + + assertThat(teamLead).isNotNull(); + assertThat(teamLead.getReportsTo()).isNull(); + List team = teamLead.getTeam(); + assertThat(team).hasSize(2); + assertThat(team).extracting("reportsTo").containsExactly(teamLead, teamLead); + } + + private Employee employee(String name, Employee reportsTo) { + Employee employee = new Employee(); + employee.setName(name); + employee.setReportsTo(reportsTo); + return employee; + } + +} diff --git a/example/spring-boot-with-lombok/src/test/java/io/github/linpeilie/ProductMapperTest.java b/example/spring-boot-with-lombok/src/test/java/io/github/linpeilie/ProductMapperTest.java new file mode 100644 index 0000000000000000000000000000000000000000..209ae67840ecf98a3800a3fc4e18dd85bfae8f94 --- /dev/null +++ b/example/spring-boot-with-lombok/src/test/java/io/github/linpeilie/ProductMapperTest.java @@ -0,0 +1,40 @@ +package io.github.linpeilie; + +import io.github.linpeilie.model.Product; +import io.github.linpeilie.model.ProductDto; +import io.github.linpeilie.model.ProductProperty; +import java.util.Arrays; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +public class ProductMapperTest { + + @Autowired + private Converter converter; + + @Test + public void test() { + Product product = new Product(); + product.setId(1L); + product.setName("Product"); + + ProductProperty productProperty1 = new ProductProperty(); + productProperty1.setId(1L); + productProperty1.setName("ProductProperty"); + productProperty1.setProduct(product); + + ProductProperty productProperty2 = new ProductProperty(); + productProperty2.setId(1L); + productProperty2.setName("ProductProperty"); + productProperty2.setProduct(product); + + product.setProductPropertyList(Arrays.asList(productProperty1, productProperty2)); + + ProductDto productDto = converter.convert(product, ProductDto.class); + + System.out.println(productDto); + } + +} diff --git a/example/spring-boot-with-lombok/src/test/java/io/github/linpeilie/QuickStartTest.java b/example/spring-boot-with-lombok/src/test/java/io/github/linpeilie/QuickStartTest.java index 7ef1639f0d6525bf2c3a72192334ae4c8fae5d5d..ee90bba0860dab5d6dca377da0d795156b0c9f95 100644 --- a/example/spring-boot-with-lombok/src/test/java/io/github/linpeilie/QuickStartTest.java +++ b/example/spring-boot-with-lombok/src/test/java/io/github/linpeilie/QuickStartTest.java @@ -2,7 +2,10 @@ package io.github.linpeilie; import cn.hutool.core.date.DateUtil; import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.StrUtil; import io.github.linpeilie.model.Car; +import io.github.linpeilie.model.EnglishRelease; +import io.github.linpeilie.model.FrenchRelease; import io.github.linpeilie.model.Goods; import io.github.linpeilie.model.GoodsDto; import io.github.linpeilie.model.GoodsStateEnum; @@ -16,6 +19,8 @@ import io.github.linpeilie.model.SVO; import io.github.linpeilie.model.Sku; import io.github.linpeilie.model.SysMenu; import io.github.linpeilie.model.SysMenuVo; +import io.github.linpeilie.model.TreeNode; +import io.github.linpeilie.model.TreeNodeDto; import io.github.linpeilie.model.User; import io.github.linpeilie.model.UserDto; import io.github.linpeilie.model.UserVO; @@ -262,4 +267,48 @@ public class QuickStartTest { System.out.println(sDto1); } + @Test + public void testConditionQualifiedByName() { + TreeNode parent = new TreeNode(); + // children + List children = new ArrayList<>(); + for (int i = 0; i < 1; i++) { + TreeNode child = new TreeNode(); + child.setParent(parent); + children.add(child); + } + parent.setChildren(children); + TreeNodeDto treeNodeDto1 = converter.convert(parent, TreeNodeDto.class); + // 当 children 为 <2 时不进行转换 + Assert.isNull(treeNodeDto1.getChildren()); + for (int i = 0; i < 99; i++) { + TreeNode child = new TreeNode(); + child.setParent(parent); + children.add(child); + } + TreeNodeDto treeNodeDto2 = converter.convert(parent, TreeNodeDto.class); + Assert.equals(treeNodeDto2.getChildren().size(), 100); + + treeNodeDto2.getChildren().forEach(child -> { + Assert.equals(child.getParent(), treeNodeDto2); + }); + } + + @Test + public void testQualifiedByName() { + EnglishRelease englishRelease = new EnglishRelease(); + englishRelease.setTitle("Algorithms, 4th Edition"); + FrenchRelease frenchRelease1 = converter.convert(englishRelease, FrenchRelease.class); + Assert.equals(frenchRelease1.getTitle(), "Inconnu et inconnu"); + englishRelease.setTitle("One Hundred Years of Solitude"); + FrenchRelease frenchRelease2 = converter.convert(englishRelease, FrenchRelease.class); + Assert.equals(frenchRelease2.getTitle(), "Cent ans de solitude"); + + EnglishRelease englishRelease1 = converter.convert(frenchRelease1, EnglishRelease.class); + Assert.equals(englishRelease1.getTitle(), "Unknown"); + frenchRelease2.setTitle("Cent ans de solitude"); + EnglishRelease englishRelease2 = converter.convert(frenchRelease2, EnglishRelease.class); + Assert.equals(englishRelease2.getTitle(), "One Hundred Years of Solitude"); + } + } diff --git a/example/spring-boot/pom.xml b/example/spring-boot/pom.xml index 428f4ae3d8831b7713448f383a26d9e7e038e0a9..a97857c74aa494de4945289ac6cb7cef08af148f 100644 --- a/example/spring-boot/pom.xml +++ b/example/spring-boot/pom.xml @@ -42,6 +42,10 @@ io.github.linpeilie mapstruct-plus-spring-boot-starter + + cn.hutool + hutool-all + diff --git a/example/spring-boot/src/test/java/io/github/linpeilie/QuickStartTest.java b/example/spring-boot/src/test/java/io/github/linpeilie/QuickStartTest.java index 596685d644b41547f7e92c7f35bd84523548b9f4..e8a89d526ad8ab1e2c1a65cd1f02de242f9c9ae7 100644 --- a/example/spring-boot/src/test/java/io/github/linpeilie/QuickStartTest.java +++ b/example/spring-boot/src/test/java/io/github/linpeilie/QuickStartTest.java @@ -1,6 +1,5 @@ package io.github.linpeilie; -import cn.hutool.core.date.DateUtil; import io.github.linpeilie.model.Goods; import io.github.linpeilie.model.GoodsDto; import io.github.linpeilie.model.MapModelA; @@ -27,14 +26,14 @@ public class QuickStartTest { private Converter converter; @Test - public void test() { + public void test() throws ParseException { Map mapModel1 = new HashMap<>(); mapModel1.put("str", "1jkf1ijkj3f"); mapModel1.put("i1", 111); mapModel1.put("l2", 11231); Map mapModel2 = new HashMap<>(); - mapModel2.put("date", DateUtil.parse("2023-02-23 01:03:23")); + mapModel2.put("date", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse("2023-02-23 01:03:23")); mapModel1.put("mapModelB", mapModel2); @@ -99,7 +98,7 @@ public class QuickStartTest { } @Test - public void multiClassConvertTest() { + public void multiClassConvertTest() throws ParseException { User user = new User(); List list = new ArrayList<>(); list.add("1"); @@ -110,7 +109,7 @@ public class QuickStartTest { user.setUsername("Nick"); user.setAge(12); user.setYoung(true); - user.setBirthday(DateUtil.parseDateTime("2023-02-23 02:01:43")); + user.setBirthday(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse("2023-02-23 02:01:43")); user.setAssets(123.234); user.setVoField("vofieldfff"); user.setMoney(12543.123); diff --git a/mapstruct-plus-object-convert/pom.xml b/mapstruct-plus-object-convert/pom.xml new file mode 100644 index 0000000000000000000000000000000000000000..d931c4b0c4431a57cd80115382bdeb974a4f4572 --- /dev/null +++ b/mapstruct-plus-object-convert/pom.xml @@ -0,0 +1,30 @@ + + + 4.0.0 + + io.github.linpeilie + mapstruct-plus-pom + ${mapstruct-plus.version} + ../pom.xml + + + mapstruct-plus-object-convert + + + 8 + 8 + UTF-8 + + + + + cn.hutool + hutool-core + true + provided + + + + \ No newline at end of file diff --git a/mapstruct-plus/src/main/java/io/github/linpeilie/map/MapObjectConvert.java b/mapstruct-plus-object-convert/src/main/java/io/github/linpeilie/map/MapObjectConvert.java similarity index 100% rename from mapstruct-plus/src/main/java/io/github/linpeilie/map/MapObjectConvert.java rename to mapstruct-plus-object-convert/src/main/java/io/github/linpeilie/map/MapObjectConvert.java diff --git a/mapstruct-plus-processor/pom.xml b/mapstruct-plus-processor/pom.xml index bd6dcdff6aca59ac2ed6ce3a2d8ac982e5f77471..e11702d49d68c7da22e132a203471aa4666696ab 100644 --- a/mapstruct-plus-processor/pom.xml +++ b/mapstruct-plus-processor/pom.xml @@ -27,10 +27,6 @@ com.baidu.lbsyun javapoet - - org.apache.commons - commons-lang3 - org.mapstruct mapstruct-processor 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 889ff3eb8931902fb98b0dec1e2192604a8d3155..21b45ada6f88c7ef80102ea8b2913b8f83000266 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 @@ -9,35 +9,78 @@ import com.squareup.javapoet.ParameterizedTypeName; import com.squareup.javapoet.TypeName; import com.squareup.javapoet.TypeSpec; import io.github.linpeilie.processor.metadata.AbstractAdapterMethodMetadata; +import io.github.linpeilie.processor.metadata.AdapterMapMethodMetadata; +import io.github.linpeilie.processor.metadata.AdapterMethodMetadata; +import io.github.linpeilie.processor.utils.ClassUtil; import java.io.IOException; import java.io.Writer; +import java.util.ArrayList; import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.Modifier; -import javax.lang.model.element.TypeElement; import static javax.tools.Diagnostic.Kind.ERROR; public abstract class AbstractAdapterMapperGenerator { + protected static final String PARAM__PARAMETER_NAME = "param"; + + protected static final String CONTEXT__PARAMETER_NAME = "context"; + public void write(ProcessingEnvironment processingEnv, Collection adapterMethods, - String adapterClassName) { + String adapterClassName, + boolean cycleAvoiding) { + write(processingEnv, createAdapterTypeSpec(adapterClassName, adapterMethods, cycleAvoiding)); + } + + private void write(ProcessingEnvironment processingEnv, TypeSpec typeSpec) { + if (typeSpec == null) { + return; + } // write Adapter try (final Writer writer = processingEnv.getFiler() - .createSourceFile(adapterPackage() + "." + adapterClassName) + .createSourceFile(adapterPackage() + "." + typeSpec.name) .openWriter()) { - JavaFile.builder(adapterPackage(), createTypeSpec(adapterMethods, adapterClassName)) + JavaFile.builder(adapterPackage(), typeSpec) .build() .writeTo(writer); } catch (IOException e) { processingEnv.getMessager() - .printMessage(ERROR, "Error while opening " + adapterClassName + " output file: " + e.getMessage()); + .printMessage(ERROR, "Error while opening " + typeSpec.name + " output file: " + e.getMessage()); } } - protected abstract TypeSpec createTypeSpec(Collection adapterMethods, - String adapterClassName); + protected TypeSpec createAdapterTypeSpec(String adapterClassName, + Collection adapterMethods, + boolean cycleAvoiding) { + List methods = adapterMethods.stream() + .filter(method -> !cycleAvoiding || method.needCycleAvoiding()) + .map(method -> buildProxyMethod(method, cycleAvoiding)) + .flatMap(Collection::stream) + .collect(Collectors.toList()); + if (methods.isEmpty()) { + return null; + } + return createTypeSpec(methods, adapterClassName, + cycleAvoiding ? ClassName.get(adapterPackage(), AutoMapperProperties.getAdapterClassName()) : null); + } + + protected TypeSpec createTypeSpec(List methods, String adapterClassName, ClassName superClass) { + TypeSpec.Builder adapterBuilder = TypeSpec.classBuilder(ClassName.get(adapterPackage(), adapterClassName)) + .addModifiers(Modifier.PUBLIC); + + if (superClass != null) { + adapterBuilder.superclass(superClass); + } + + // adapter methods + adapterBuilder.addMethods(methods); + + return adapterBuilder.build(); + } protected String adapterPackage() { return AutoMapperProperties.getAdapterPackage(); @@ -48,27 +91,151 @@ public abstract class AbstractAdapterMapperGenerator { return source; } if ("java.util.Map".contentEquals(source.toString())) { - return ParameterizedTypeName.get((ClassName) source, - ClassName.get("java.lang", "String"), + return ParameterizedTypeName.get((ClassName) source, ClassName.get("java.lang", "String"), ClassName.get("java.lang", "Object")); } return source; } - protected MethodSpec buildProxyMethod(AbstractAdapterMethodMetadata adapterMethodMetadata) { - CodeBlock targetCode = adapterMethodMetadata.isStatic() ? CodeBlock.of("return $T.$N($N);", - adapterMethodMetadata.getMapper(), adapterMethodMetadata.getMapperMethodName(), - "param") : proxyMethodTarget(adapterMethodMetadata); - ParameterSpec parameterSpec = ParameterSpec.builder( - wrapperTypeName(adapterMethodMetadata.getSource()), "param").build(); + private List buildProxyMethod(AbstractAdapterMethodMetadata adapterMethod, boolean cycleAvoiding) { + List methodSpecs = new ArrayList<>(); + if (cycleAvoiding) { + methodSpecs.addAll(buildCycleAvoidingProxyMethod(adapterMethod)); + } else { + methodSpecs.addAll(buildDefaultProxyMethod(adapterMethod, null)); + } + return methodSpecs; + } + + protected List buildDefaultProxyMethod(AbstractAdapterMethodMetadata adapterMethodMetadata, + ClassName annotation) { + List methodSpecs = new ArrayList<>(); + + ParameterSpec parameterSpec = + ParameterSpec.builder(wrapperTypeName(adapterMethodMetadata.getSource()), PARAM__PARAMETER_NAME).build(); + MethodSpec methodSpecForSingle = + buildDefaultProxyMethod(adapterMethodMetadata, parameterSpec, adapterMethodMetadata.getReturn(), + annotation); + methodSpecs.add(methodSpecForSingle); + + // 自定义类型转换 + if (adapterMethodMetadata instanceof AdapterMethodMetadata) { + ParameterSpec listParameter = ParameterSpec.builder( + ParameterizedTypeName.get(ClassName.get("java.util", "List"), adapterMethodMetadata.getSource()), + PARAM__PARAMETER_NAME + ).build(); + MethodSpec methodSpecForList = + buildDefaultProxyMethod(adapterMethodMetadata, listParameter, + ParameterizedTypeName.get(ClassName.get("java.util", "List"), adapterMethodMetadata.getReturn()), + annotation); + methodSpecs.add(methodSpecForList); + } + // 自定义类型与 Map 进行转换 + else if (adapterMethodMetadata instanceof AdapterMapMethodMetadata) { + methodSpecs.add(buildObjConversionProxyMethod(adapterMethodMetadata, annotation)); + } + + return methodSpecs; + } + + private MethodSpec buildObjConversionProxyMethod(AbstractAdapterMethodMetadata adapterMethod, + ClassName annotation) { + CodeBlock code = CodeBlock.builder() + .add("if($N == null) {\n", PARAM__PARAMETER_NAME) + .add(" return null;\n") + .add("}\n") + .add("if($N instanceof $T) {\n", PARAM__PARAMETER_NAME, ClassName.get("java.util", "Map")) + .add(" return $N((Map<$T, $T>) $N);\n", + adapterMethod.getMethodName(), ClassName.get("java.lang", "String"), + ClassName.get("java.lang", "Object"), PARAM__PARAMETER_NAME) + .add("}\n") + .add("return null;\n") + .build(); + MethodSpec.Builder methodSpecBuilder = + MethodSpec.methodBuilder("objTo" + ClassUtil.simplifyQualifiedName(adapterMethod.getReturn().toString())) + .addModifiers(Modifier.PUBLIC) + .addParameter( + ParameterSpec.builder(ClassName.get("java.lang", "Object"), PARAM__PARAMETER_NAME) + .build() + ) + .returns(adapterMethod.getReturn()) + .addCode(code); + if (annotation != null) { + methodSpecBuilder.addAnnotation(annotation); + } + return methodSpecBuilder.build(); + } + + private MethodSpec buildDefaultProxyMethod(AbstractAdapterMethodMetadata adapterMethodMetadata, + ParameterSpec parameterSpec, + TypeName returns, + ClassName annotation) { + CodeBlock targetCode = adapterMethodMetadata.isStatic() + ? CodeBlock.of("return $T.$N($N);", adapterMethodMetadata.getMapper(), + adapterMethodMetadata.getMapperMethodName(), "param") + : proxyMethodTarget(adapterMethodMetadata); + MethodSpec.Builder methodSpecBuilder = MethodSpec.methodBuilder(adapterMethodMetadata.getMethodName()) + .addModifiers(Modifier.PUBLIC) + .addParameter(parameterSpec) + .returns(returns) + .addCode(targetCode); + if (annotation != null) { + methodSpecBuilder.addAnnotation(annotation); + } + return methodSpecBuilder.build(); + } + + protected List buildCycleAvoidingProxyMethod(AbstractAdapterMethodMetadata adapterMethodMetadata) { + + // default impl + List defaultMethods = buildDefaultProxyMethod(adapterMethodMetadata, + ClassName.get(ContextConstants.DoIgnore.packageName, ContextConstants.DoIgnore.className)); + List methodSpecs = new ArrayList<>(defaultMethods); + + ParameterSpec parameterSpec = + ParameterSpec.builder(wrapperTypeName(adapterMethodMetadata.getSource()), PARAM__PARAMETER_NAME).build(); + methodSpecs.add( + buildCycleAvoidingProxyMethod(adapterMethodMetadata, parameterSpec, adapterMethodMetadata.getReturn())); + + if (adapterMethodMetadata instanceof AdapterMethodMetadata) { + ParameterSpec listParameter = ParameterSpec.builder( + ParameterizedTypeName.get(ClassName.get("java.util", "List"), adapterMethodMetadata.getSource()), + "param" + ).build(); + MethodSpec methodSpecForList = + buildCycleAvoidingProxyMethod(adapterMethodMetadata, listParameter, + ClassName.get("java.util", "List")); + methodSpecs.add(methodSpecForList); + } + + return methodSpecs; + } + + private MethodSpec buildCycleAvoidingProxyMethod(AbstractAdapterMethodMetadata adapterMethodMetadata, + ParameterSpec parameterSpec, + TypeName returns + ) { + CodeBlock targetCode = adapterMethodMetadata.isStatic() + ? CodeBlock.of("return $T.$N($N, $N);", adapterMethodMetadata.getMapper(), + adapterMethodMetadata.getMapperMethodName(), "param", CONTEXT__PARAMETER_NAME) + : cycleAvoidingMethodTarget(adapterMethodMetadata); + ParameterSpec contextParameterSpec = + ParameterSpec.builder( + ClassName.get("io.github.linpeilie", "CycleAvoidingMappingContext"), + CONTEXT__PARAMETER_NAME) + .addAnnotation(ClassName.get("org.mapstruct", "Context")) + .build(); return MethodSpec.methodBuilder(adapterMethodMetadata.getMethodName()) .addModifiers(Modifier.PUBLIC) .addParameter(parameterSpec) - .returns(adapterMethodMetadata.getReturn()) + .addParameter(contextParameterSpec) + .returns(returns) .addCode(targetCode) .build(); } protected abstract CodeBlock proxyMethodTarget(AbstractAdapterMethodMetadata adapterMethodMetadata); + protected abstract CodeBlock cycleAvoidingMethodTarget(AbstractAdapterMethodMetadata adapterMethodMetadata); + } 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 be2d558650d9e23ab0be6e4f0873ea0410b8e389..4c320dd1adb80d629c7f606f5a0db43c2e5ae7c3 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,9 +1,5 @@ package io.github.linpeilie.processor; -import cn.hutool.core.collection.CollectionUtil; -import cn.hutool.core.exceptions.ExceptionUtil; -import cn.hutool.core.util.ObjectUtil; -import cn.hutool.core.util.StrUtil; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.TypeName; import io.github.linpeilie.ComponentModelConstant; @@ -31,11 +27,16 @@ import io.github.linpeilie.processor.metadata.AutoEnumMapperMetadata; import io.github.linpeilie.processor.metadata.AutoMapMapperMetadata; import io.github.linpeilie.processor.metadata.AutoMapperMetadata; import io.github.linpeilie.processor.metadata.AutoMappingMetadata; +import io.github.linpeilie.processor.utils.ExceptionUtils; +import io.github.linpeilie.processor.utils.ObjectUtils; +import io.github.linpeilie.utils.CollectionUtils; +import io.github.linpeilie.utils.StrUtil; import java.io.IOException; import java.io.Writer; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -46,7 +47,6 @@ 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; @@ -63,13 +63,6 @@ import javax.lang.model.type.TypeMirror; import javax.tools.Diagnostic; import org.mapstruct.MappingConstants; -import static io.github.linpeilie.processor.Constants.AUTO_ENUM_MAPPER_ANNOTATION; -import static io.github.linpeilie.processor.Constants.AUTO_MAPPERS_ANNOTATION; -import static io.github.linpeilie.processor.Constants.AUTO_MAPPER_ANNOTATION; -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; @@ -83,22 +76,17 @@ import static io.github.linpeilie.processor.ProcessorOptions.UNMAPPED_SOURCE_POL import static io.github.linpeilie.processor.ProcessorOptions.UNMAPPED_TARGET_POLICY; import static javax.tools.Diagnostic.Kind.ERROR; -@SupportedAnnotationTypes({AUTO_MAPPER_ANNOTATION, AUTO_MAPPERS_ANNOTATION, AUTO_MAP_MAPPER_ANNOTATION, - AUTO_ENUM_MAPPER_ANNOTATION, MAPPER_CONFIG_ANNOTATION, COMPONENT_MODEL_CONFIG_ANNOTATION, - MAPPER_ANNOTATION}) -@SupportedOptions({ - MAPPER_CONFIG_CLASS, - MAPPER_PACKAGE, - UNMAPPED_SOURCE_POLICY, - UNMAPPED_TARGET_POLICY, - NULL_VALUE_MAPPING_STRATEGY, - NULL_VALUE_PROPERTY_MAPPING_STRATEGY, - BUILDER_BUILD_METHOD, - BUILDER_DISABLE_BUILDER, - ADAPTER_PACKAGE, - ADAPTER_CLASS_NAME, - MAP_ADAPTER_CLASS_NAME, -}) +@SupportedAnnotationTypes({ + ContextConstants.Annotations.autoMapper, + ContextConstants.Annotations.autoMappers, + ContextConstants.Annotations.autoMapMapper, + ContextConstants.Annotations.autoEnumMapper, + ContextConstants.Annotations.mapperConfig, + ContextConstants.Annotations.componentModel, + ContextConstants.Annotations.mapper}) +@SupportedOptions({MAPPER_CONFIG_CLASS, MAPPER_PACKAGE, UNMAPPED_SOURCE_POLICY, UNMAPPED_TARGET_POLICY, + NULL_VALUE_MAPPING_STRATEGY, NULL_VALUE_PROPERTY_MAPPING_STRATEGY, BUILDER_BUILD_METHOD, + BUILDER_DISABLE_BUILDER, ADAPTER_PACKAGE, ADAPTER_CLASS_NAME, MAP_ADAPTER_CLASS_NAME,}) public class AutoMapperProcessor extends AbstractProcessor { private static final ClassName MAPPING_DEFAULT_TARGET = ClassName.get("io.github.linpeilie", "DefaultMapping"); @@ -121,46 +109,39 @@ public class AutoMapperProcessor extends AbstractProcessor { private Messager messager; - private Filer filer; - public AutoMapperProcessor() { this.mapperGenerator = new AutoMapperGenerator(); this.mapperConfigGenerator = new MapperConfigGenerator(); } private boolean isAutoMapperAnnotation(TypeElement annotation) { - return AUTO_MAPPER_ANNOTATION.contentEquals(annotation.getQualifiedName()); + return ContextConstants.Annotations.autoMapper.contentEquals(annotation.getQualifiedName()); } private boolean isAutoMappersAnnotation(TypeElement annotation) { - return AUTO_MAPPERS_ANNOTATION.contentEquals(annotation.getQualifiedName()); + return ContextConstants.Annotations.autoMappers.contentEquals(annotation.getQualifiedName()); } private boolean isAutoMapMapperAnnotation(TypeElement annotation) { - return AUTO_MAP_MAPPER_ANNOTATION.contentEquals(annotation.getQualifiedName()); + return ContextConstants.Annotations.autoMapMapper.contentEquals(annotation.getQualifiedName()); } private boolean isAutoEnumMapperAnnotation(TypeElement annotation) { - return AUTO_ENUM_MAPPER_ANNOTATION.contentEquals(annotation.getQualifiedName()); + return ContextConstants.Annotations.autoEnumMapper.contentEquals(annotation.getQualifiedName()); } private boolean isMapperConfigAnnotation(TypeElement annotation) { - return MAPPER_CONFIG_ANNOTATION.contentEquals(annotation.getQualifiedName()); - } - - private boolean isMapperAnnotation(TypeElement annotation) { - return MAPPER_ANNOTATION.contentEquals(annotation.getQualifiedName()); + return ContextConstants.Annotations.mapperConfig.contentEquals(annotation.getQualifiedName()); } private boolean isComponentModelConfigAnnotation(TypeElement annotation) { - return COMPONENT_MODEL_CONFIG_ANNOTATION.contentEquals(annotation.getQualifiedName()); + return ContextConstants.Annotations.componentModel.contentEquals(annotation.getQualifiedName()); } @Override public synchronized void init(final ProcessingEnvironment processingEnv) { super.init(processingEnv); messager = processingEnv.getMessager(); - filer = processingEnv.getFiler(); } @Override @@ -168,7 +149,7 @@ public class AutoMapperProcessor extends AbstractProcessor { try { doProcess(annotations, roundEnv); } catch (Exception e) { - messager.printMessage(ERROR, ExceptionUtil.stacktraceToString(e)); + messager.printMessage(ERROR, ExceptionUtils.getStackTrace(e)); } return false; @@ -200,25 +181,27 @@ public class AutoMapperProcessor extends AbstractProcessor { // AutoMapMapper final TypeElement autoMapMapperAnnotation = - processingEnv.getElementUtils().getTypeElement(AUTO_MAP_MAPPER_ANNOTATION); + processingEnv.getElementUtils().getTypeElement(ContextConstants.Annotations.autoMapMapper); processAutoMapMapperAnnotation(roundEnv, autoMapMapperAnnotation); // AutoEnumMapper final TypeElement autoEnumMapperAnnotation = - processingEnv.getElementUtils().getTypeElement(AUTO_ENUM_MAPPER_ANNOTATION); + processingEnv.getElementUtils().getTypeElement(ContextConstants.Annotations.autoEnumMapper); processAutoEnumMapperAnnotation(roundEnv, autoEnumMapperAnnotation); // AutoMapper - final TypeElement autoMapperAnnotation = processingEnv.getElementUtils().getTypeElement(AUTO_MAPPER_ANNOTATION); + final TypeElement autoMapperAnnotation = + processingEnv.getElementUtils().getTypeElement(ContextConstants.Annotations.autoMapper); processAutoMapperAnnotation(roundEnv, autoMapperAnnotation); // AutoMappers final TypeElement autoMappersAnnotation = - processingEnv.getElementUtils().getTypeElement(AUTO_MAPPERS_ANNOTATION); + processingEnv.getElementUtils().getTypeElement(ContextConstants.Annotations.autoMappers); processAutoMappersAnnotation(roundEnv, autoMappersAnnotation); // custom mapper - final TypeElement mapperAnnotation = processingEnv.getElementUtils().getTypeElement(MAPPER_ANNOTATION); + final TypeElement mapperAnnotation = + processingEnv.getElementUtils().getTypeElement(ContextConstants.Mapper.qualifiedClassName); processMapperAnnotation(roundEnv, mapperAnnotation); // 生成类 @@ -238,7 +221,7 @@ public class AutoMapperProcessor extends AbstractProcessor { } final List elements = getElementAndMergeHistory(roundEnv, annotation, - new BuildCollator(processingEnv, Constants.MAPPERS_FILE_NAME)); + new BuildCollator(processingEnv, ContextConstants.MetaInf.mappers)); elements.forEach(element -> customMapperList.add(element.asType())); } @@ -248,7 +231,7 @@ public class AutoMapperProcessor extends AbstractProcessor { return; } final List elements = getElementAndMergeHistory(roundEnv, annotation, - new BuildCollator(processingEnv, Constants.ENUM_MAPPERS_FILE_NAME)); + new BuildCollator(processingEnv, ContextConstants.MetaInf.enumMappers)); elements.stream() .map(this::buildAutoEnumMapperMetadata) .filter(Objects::nonNull) @@ -277,14 +260,12 @@ public class AutoMapperProcessor extends AbstractProcessor { final AdapterEnumMethodMetadata toValueProxyMethod = new AdapterEnumMethodMetadata(autoEnumMapperMetadata.getSourceClassName(), ClassName.get(autoEnumMapperMetadata.mapperPackage(), autoEnumMapperMetadata.mapperName()), - autoEnumMapperMetadata.toValueMethodName(), - autoEnumMapperMetadata.getReturnType()); + autoEnumMapperMetadata.toValueMethodName(), autoEnumMapperMetadata.getReturnType()); // toEnum final AdapterEnumMethodMetadata toEnumProxyMethod = new AdapterEnumMethodMetadata(autoEnumMapperMetadata.getReturnType(), ClassName.get(autoEnumMapperMetadata.mapperPackage(), autoEnumMapperMetadata.mapperName()), - autoEnumMapperMetadata.toEnumMethodName(), - autoEnumMapperMetadata.getSourceClassName()); + autoEnumMapperMetadata.toEnumMethodName(), autoEnumMapperMetadata.getSourceClassName()); methodMap.putIfAbsent( autoEnumMapperMetadata.getSourceClassName().simpleName() + toValueProxyMethod.getMapperMethodName(), toValueProxyMethod); @@ -304,8 +285,8 @@ public class AutoMapperProcessor extends AbstractProcessor { if (!ElementKind.METHOD.equals(ele.getKind())) { continue; } - boolean isGetter = StrUtil.equalsIgnoreCase(ele.getSimpleName(), "get" + enumCodeFieldName) - || StrUtil.equalsIgnoreCase(ele.getSimpleName(), "is" + enumCodeFieldName); + boolean isGetter = StrUtil.equalsIgnoreCase(ele.getSimpleName(), "get" + enumCodeFieldName) || + StrUtil.equalsIgnoreCase(ele.getSimpleName(), "is" + enumCodeFieldName); if (isGetter) { enumCodeGetterElement = ele; break; @@ -331,18 +312,24 @@ public class AutoMapperProcessor extends AbstractProcessor { } final List elements = getElementAndMergeHistory(roundEnv, annotation, - new BuildCollator(processingEnv, Constants.AUTO_MAP_MAPPERS_FILE_NAME)); + new BuildCollator(processingEnv, ContextConstants.MetaInf.autoMapMappers)); elements.stream() - .map(ele -> buildAutoMapMapperMetadata((TypeElement) ele)) + .map(this::buildAutoMapMapperMetadata) .filter(Objects::nonNull) .forEach(metadata -> { this.writeAutoMapperClassFile(metadata); addAdapterMapMethod(metadata); }); - adapterMapperGenerator.write(processingEnv, mapMethodMap.values(), - AutoMapperProperties.getMapAdapterClassName()); + if (mapMethodMap.isEmpty()) { + return; + } + + adapterMapperGenerator.write(processingEnv, + mapMethodMap.values(), + AutoMapperProperties.getMapAdapterClassName(), + false); mapperConfigGenerator.write(processingEnv, AutoMapperProperties.getMapConfigClassName(), AutoMapperProperties.getMapAdapterClassName(), null); @@ -352,18 +339,20 @@ public class AutoMapperProcessor extends AbstractProcessor { if (element.getAnnotation(AutoMapMapper.class) == null) { return null; } - ClassName source = ClassName.get("java.util", "Map"); + ClassName source = ClassName.get(ContextConstants.Map.packageName, ContextConstants.Map.className); ClassName target = ClassName.get(element); - List uses = Arrays.asList(ClassName.get("io.github.linpeilie.map", "MapObjectConvert")); + List uses = Collections.singletonList( + ClassName.get(ContextConstants.MapObjectConvert.packageName, ContextConstants.MapObjectConvert.className)); final AutoMapperMetadata autoMapperMetadata = new AutoMapMapperMetadata(); autoMapperMetadata.setTargetClassName(target); autoMapperMetadata.setSourceClassName(source); autoMapperMetadata.setUsesClassNameList(uses); - autoMapperMetadata.setSuperClass(ClassName.get("io.github.linpeilie", "BaseMapMapper")); + autoMapperMetadata.setSuperClass( + ClassName.get(ContextConstants.BaseMapMapper.packageName, ContextConstants.BaseMapMapper.className)); autoMapperMetadata.setSuperGenerics(new ClassName[] {target}); - autoMapperMetadata.setMapstructConfigClass(ClassName.get(AutoMapperProperties.getConfigPackage(), - AutoMapperProperties.getMapConfigClassName())); + autoMapperMetadata.setMapstructConfigClass( + ClassName.get(AutoMapperProperties.getConfigPackage(), AutoMapperProperties.getMapConfigClassName())); return autoMapperMetadata; } @@ -373,8 +362,6 @@ public class AutoMapperProcessor extends AbstractProcessor { } addAdapterMapMethod(metadata.getSourceClassName(), metadata.getTargetClassName(), metadata.mapperClass(), false); - addAdapterMapMethod(ClassName.get("java.lang", "Object"), metadata.getTargetClassName(), - metadata.mapperClass(), true); } private void loadMapperConfig(MapperConfig mapperConfig) { @@ -411,11 +398,11 @@ 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); + final BuildCollator buildCollator = new BuildCollator(processingEnv, ContextConstants.MetaInf.mapperConfig); // load previous mapper config final List typeElements = buildCollator.getRecords(); - if (CollectionUtil.isNotEmpty(typeElements)) { + if (CollectionUtils.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)); @@ -423,16 +410,14 @@ public class AutoMapperProcessor extends AbstractProcessor { // annotation --> MapperConfig final TypeElement mapperConfigAnnotation = - processingEnv.getElementUtils().getTypeElement(MAPPER_CONFIG_ANNOTATION); + processingEnv.getElementUtils().getTypeElement(ContextConstants.Annotations.mapperConfig); if (mapperConfigAnnotation != null) { final Optional mapperConfigOptional = - roundEnv.getElementsAnnotatedWith(mapperConfigAnnotation) - .stream() - .findFirst(); + roundEnv.getElementsAnnotatedWith(mapperConfigAnnotation).stream().findFirst(); if (mapperConfigOptional.isPresent()) { loadMapperConfig(mapperConfigOptional.get().getAnnotation(MapperConfig.class)); // record - buildCollator.writeTypeElements(CollectionUtil.newArrayList((TypeElement) mapperConfigOptional.get())); + buildCollator.writeTypeElements(Collections.singletonList((TypeElement) mapperConfigOptional.get())); } } @@ -473,13 +458,10 @@ public class AutoMapperProcessor extends AbstractProcessor { return; } final List elements = getElementAndMergeHistory(roundEnv, annotation, - new BuildCollator(processingEnv, Constants.AUTO_MAPPER_FILE_NAME)); + new BuildCollator(processingEnv, ContextConstants.MetaInf.autoMapper)); - final List autoMapperMetadataList = elements - .stream() - .map(this::buildAutoMapperMetadata) - .filter(Objects::nonNull) - .collect(Collectors.toList()); + final List autoMapperMetadataList = + elements.stream().map(this::buildAutoMapperMetadata).filter(Objects::nonNull).collect(Collectors.toList()); mapperList.addAll(autoMapperMetadataList); } @@ -489,8 +471,7 @@ public class AutoMapperProcessor extends AbstractProcessor { return; } final List autoMapperMetadata = getElementAndMergeHistory(roundEnv, annotation, - new BuildCollator(processingEnv, Constants.AUTO_MAPPERS_FILE_NAME)) - .stream() + new BuildCollator(processingEnv, ContextConstants.MetaInf.autoMappers)).stream() .map(this::buildAutoMapperMetadataByAutoMappers) .filter(Objects::nonNull) .flatMap(Collection::stream) @@ -506,7 +487,7 @@ public class AutoMapperProcessor extends AbstractProcessor { if (!autoMapperMetadata.isReverseConvertGenerate()) { return; } - boolean defineReverseMapping = CollectionUtil.isNotEmpty(autoMapperMetadata.getFieldReverseMappingList()); + boolean defineReverseMapping = CollectionUtils.isNotEmpty(autoMapperMetadata.getFieldReverseMappingList()); final AutoMapperMetadata reverseMapperMetadata = reverseMapper(autoMapperMetadata); if (defineReverseMapping) { addMapper(reverseMapperMetadata); @@ -526,21 +507,52 @@ public class AutoMapperProcessor extends AbstractProcessor { addAdapterMethod(metadata); }); - adapterMapperGenerator.write(processingEnv, methodMap.values(), AutoMapperProperties.getAdapterClassName()); + if (methodMap.isEmpty()) { + return; + } + + adapterMapperGenerator.write(processingEnv, + methodMap.values(), + AutoMapperProperties.getAdapterClassName(), + false); - mapperConfigGenerator.write(processingEnv, AutoMapperProperties.getConfigClassName(), - AutoMapperProperties.getAdapterClassName(), customMapperList); + mapperConfigGenerator.write(processingEnv, + AutoMapperProperties.getConfigClassName(), + AutoMapperProperties.getAdapterClassName(), + customMapperList); + + boolean needCycleAvoiding = methodMap.values().stream().anyMatch( + AbstractAdapterMethodMetadata::needCycleAvoiding); + + if (needCycleAvoiding) { + adapterMapperGenerator.write(processingEnv, + methodMap.values(), + AutoMapperProperties.getCycleAvoidingAdapterClassName(), + true); + mapperConfigGenerator.write(processingEnv, + AutoMapperProperties.getCycleAvoidingConfigClassName(), + AutoMapperProperties.getCycleAvoidingAdapterClassName(), + customMapperList); + } } private AutoMapperMetadata reverseMapper(AutoMapperMetadata autoMapperMetadata) { - AutoMapperMetadata reverseMapperMetadata = initAutoMapperMetadata( - autoMapperMetadata.getTargetClassName(), autoMapperMetadata.getSourceClassName()); + AutoMapperMetadata reverseMapperMetadata = + initAutoMapperMetadata(autoMapperMetadata.getTargetClassName(), + autoMapperMetadata.getSourceClassName(), + autoMapperMetadata.isCycleAvoiding()); reverseMapperMetadata.setConvertGenerate(autoMapperMetadata.isReverseConvertGenerate()); reverseMapperMetadata.setUsesClassNameList(autoMapperMetadata.getUsesClassNameList()); reverseMapperMetadata.setImportsClassNameList(autoMapperMetadata.getImportsClassNameList()); - reverseMapperMetadata.setMapstructConfigClass( - ClassName.get(AutoMapperProperties.getConfigPackage(), AutoMapperProperties.getConfigClassName())); - if (CollectionUtil.isNotEmpty(autoMapperMetadata.getFieldReverseMappingList())) { + reverseMapperMetadata.setCycleAvoiding(autoMapperMetadata.isCycleAvoiding()); + if (reverseMapperMetadata.isCycleAvoiding()) { + reverseMapperMetadata.setSuperClass(ClassName.get(ContextConstants.BaseCycleAvoidingMapper.packageName, + ContextConstants.BaseCycleAvoidingMapper.className)); + } else { + reverseMapperMetadata.setSuperClass( + ClassName.get(ContextConstants.BaseMapper.packageName, ContextConstants.BaseMapper.className)); + } + if (CollectionUtils.isNotEmpty(autoMapperMetadata.getFieldReverseMappingList())) { reverseMapperMetadata.setFieldMappingList(autoMapperMetadata.getFieldReverseMappingList()); } else { // 需要继承的属性 @@ -574,8 +586,9 @@ public class AutoMapperProcessor extends AbstractProcessor { if (metadata == null) { return; } - AdapterMethodMetadata adapterMethodMetadata = AdapterMethodMetadata.newInstance( - metadata.getSourceClassName(), metadata.getTargetClassName(), metadata.mapperClass()); + AdapterMethodMetadata adapterMethodMetadata = + AdapterMethodMetadata.newInstance(metadata.getSourceClassName(), metadata.getTargetClassName(), + metadata.mapperClass(), metadata.isCycleAvoiding()); methodMap.putIfAbsent(adapterMethodMetadata.getMethodName(), adapterMethodMetadata); } @@ -585,15 +598,21 @@ public class AutoMapperProcessor extends AbstractProcessor { mapMethodMap.putIfAbsent(adapterMapMethodMetadata.getMethodName(), adapterMapMethodMetadata); } - private AutoMapperMetadata initAutoMapperMetadata(ClassName source, ClassName target) { + private AutoMapperMetadata initAutoMapperMetadata(ClassName source, ClassName target, boolean cycleAvoiding) { AutoMapperMetadata metadata = new AutoMapperMetadata(); metadata.setSourceClassName(source); metadata.setTargetClassName(target); - metadata.setSuperClass(ClassName.get("io.github.linpeilie", "BaseMapper")); metadata.setSuperGenerics(new ClassName[] {source, target}); - metadata.setMapstructConfigClass( - ClassName.get(AutoMapperProperties.getConfigPackage(), AutoMapperProperties.getConfigClassName())); + ClassName mapStructConfigClass; + if (cycleAvoiding) { + mapStructConfigClass = ClassName.get(AutoMapperProperties.getConfigPackage(), + AutoMapperProperties.getCycleAvoidingConfigClassName()); + } else { + mapStructConfigClass = ClassName.get(AutoMapperProperties.getConfigPackage(), + AutoMapperProperties.getConfigClassName()); + } + metadata.setMapstructConfigClass(mapStructConfigClass); return metadata; } @@ -603,16 +622,13 @@ public class AutoMapperProcessor extends AbstractProcessor { return null; } Set targetClassNames = new HashSet<>(); - return Arrays.stream(autoMappers.value()) - .filter(autoMapper -> { - ClassName className = transToClassName(autoMapper::target); - if (className == null) { - return false; - } - return targetClassNames.add(className.reflectionName()); - }) - .map(autoMapper -> buildAutoMapperMetadata(autoMapper, ele)) - .collect(Collectors.toList()); + return Arrays.stream(autoMappers.value()).filter(autoMapper -> { + ClassName className = transToClassName(autoMapper::target); + if (className == null) { + return false; + } + return targetClassNames.add(className.reflectionName()); + }).map(autoMapper -> buildAutoMapperMetadata(autoMapper, ele)).collect(Collectors.toList()); } private AutoMapperMetadata buildAutoMapperMetadata(final Element ele) { @@ -664,7 +680,7 @@ public class AutoMapperProcessor extends AbstractProcessor { List reverseMappingMetadataList = buildFieldReverseMappingMetadata((TypeElement) ele); reverseMappingMetadataList.removeIf(mappingMetadata -> !isTargetFieldMapping(target, mappingMetadata)); - AutoMapperMetadata metadata = initAutoMapperMetadata(source, target); + AutoMapperMetadata metadata = initAutoMapperMetadata(source, target, autoMapper.cycleAvoiding()); metadata.setUsesClassNameList(uses); metadata.setImportsClassNameList(importsClassNameList); @@ -672,6 +688,14 @@ public class AutoMapperProcessor extends AbstractProcessor { metadata.setFieldReverseMappingList(reverseMappingMetadataList); metadata.setConvertGenerate(autoMapper.convertGenerate()); metadata.setReverseConvertGenerate(autoMapper.reverseConvertGenerate()); + metadata.setCycleAvoiding(autoMapper.cycleAvoiding()); + if (metadata.isCycleAvoiding()) { + metadata.setSuperClass(ClassName.get(ContextConstants.BaseCycleAvoidingMapper.packageName, + ContextConstants.BaseCycleAvoidingMapper.className)); + } else { + metadata.setSuperClass( + ClassName.get(ContextConstants.BaseMapper.packageName, ContextConstants.BaseMapper.className)); + } addMapper(metadata); @@ -689,27 +713,24 @@ public class AutoMapperProcessor extends AbstractProcessor { } ReverseAutoMapping reverseAutoMapping = field.getAnnotation(ReverseAutoMapping.class); if (reverseAutoMapping != null) { - list.add(buildAutoMappingMetadata(reverseAutoMapping, field, ele)); + list.add(buildAutoMappingMetadata(reverseAutoMapping, field)); } ReverseAutoMappings reverseAutoMappings = field.getAnnotation(ReverseAutoMappings.class); if (reverseAutoMappings != null) { for (ReverseAutoMapping mapping : reverseAutoMappings.value()) { - list.add(buildAutoMappingMetadata(mapping, field, ele)); + list.add(buildAutoMappingMetadata(mapping, field)); } } } // super class - getSuperClass(ele) - .ifPresent(superClass -> list.addAll(buildFieldReverseMappingMetadata(superClass))); + getSuperClass(ele).ifPresent(superClass -> list.addAll(buildFieldReverseMappingMetadata(superClass))); list.removeIf(Objects::isNull); return list; } - private AutoMappingMetadata buildAutoMappingMetadata(ReverseAutoMapping reverseAutoMapping, - Element ele, - TypeElement type) { + private AutoMappingMetadata buildAutoMappingMetadata(ReverseAutoMapping reverseAutoMapping, Element ele) { ClassName targetClass = transToClassName(reverseAutoMapping::targetClass); if (targetClass == null) { return null; @@ -734,15 +755,18 @@ public class AutoMapperProcessor extends AbstractProcessor { metadata.setConditionExpression(reverseAutoMapping.conditionExpression()); metadata.setDateFormat(reverseAutoMapping.dateFormat()); metadata.setNumberFormat(reverseAutoMapping.numberFormat()); + metadata.setQualifiedByName(reverseAutoMapping.qualifiedByName()); + metadata.setConditionQualifiedByName(reverseAutoMapping.conditionQualifiedByName()); + metadata.setDependsOn(reverseAutoMapping.dependsOn()); return metadata; } private void addMapper(AutoMapperMetadata metadata) { if (!mapperSet.add(metadata.mapperName())) { - throw new DuplicateMapperException("An exception occurred to generate " + metadata.mapperName() - + ", check the mapping configuration for " - + metadata.getSourceClassName().reflectionName() - + " or " + metadata.getTargetClassName().reflectionName()); + throw new DuplicateMapperException("An exception occurred to generate " + metadata.mapperName() + + ", check the mapping configuration for " + + metadata.getSourceClassName().reflectionName() + " or " + + metadata.getTargetClassName().reflectionName()); } } @@ -759,25 +783,24 @@ public class AutoMapperProcessor extends AbstractProcessor { } AutoMapping autoMapping = ele.getAnnotation(AutoMapping.class); if (autoMapping != null) { - list.add(buildAutoMappingMetadata(autoMapping, ele, autoMapperEle)); + list.add(buildAutoMappingMetadata(autoMapping, ele)); } final AutoMappings autoMappings = ele.getAnnotation(AutoMappings.class); if (autoMappings != null) { for (AutoMapping autoMappingEle : autoMappings.value()) { - list.add(buildAutoMappingMetadata(autoMappingEle, ele, autoMapperEle)); + list.add(buildAutoMappingMetadata(autoMappingEle, ele)); } } } // add super class AutoMappings - getSuperClass(autoMapperEle) - .ifPresent(superClass -> list.addAll(buildFieldMappingMetadata(superClass))); + getSuperClass(autoMapperEle).ifPresent(superClass -> list.addAll(buildFieldMappingMetadata(superClass))); list.removeIf(Objects::isNull); return list; } - private AutoMappingMetadata buildAutoMappingMetadata(AutoMapping autoMapping, Element ele, TypeElement type) { + private AutoMappingMetadata buildAutoMappingMetadata(AutoMapping autoMapping, Element ele) { ClassName targetClass = transToClassName(autoMapping::targetClass); if (targetClass == null) { return null; @@ -787,7 +810,7 @@ public class AutoMapperProcessor extends AbstractProcessor { String elementName = ele.getSimpleName().toString(); if (ele.getKind() == ElementKind.METHOD) { - elementName = ObjectUtil.defaultIfBlank(StrUtil.getGeneralField(elementName), elementName); + elementName = ObjectUtils.defaultIfNull(StrUtil.getGeneralField(elementName), elementName); } if (StrUtil.isNotEmpty(autoMapping.source())) { @@ -808,6 +831,9 @@ public class AutoMapperProcessor extends AbstractProcessor { metadata.setConditionExpression(autoMapping.conditionExpression()); metadata.setDateFormat(autoMapping.dateFormat()); metadata.setNumberFormat(autoMapping.numberFormat()); + metadata.setQualifiedByName(autoMapping.qualifiedByName()); + metadata.setConditionQualifiedByName(autoMapping.conditionQualifiedByName()); + metadata.setDependsOn(autoMapping.dependsOn()); return metadata; } diff --git a/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/AutoMapperProperties.java b/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/AutoMapperProperties.java index 1a72ec8377a71c55ea94a9e9399314bfcda4b254..aa98fd124b0e7affdb6d71f7f57b00624bf13696 100644 --- a/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/AutoMapperProperties.java +++ b/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/AutoMapperProperties.java @@ -1,17 +1,14 @@ package io.github.linpeilie.processor; -import org.mapstruct.Builder; import org.mapstruct.NullValueMappingStrategy; import org.mapstruct.NullValuePropertyMappingStrategy; import org.mapstruct.ReportingPolicy; -import static io.github.linpeilie.processor.Constants.*; - public class AutoMapperProperties { private static String mapperPackage; - private static String componentModel = DEFAULT_COMPONENT_MODEL; + private static String componentModel = ContextConstants.ComponentModelConfig.defaultComponentModel; private static ReportingPolicy unmappedSourcePolicy = ReportingPolicy.IGNORE; @@ -25,17 +22,19 @@ public class AutoMapperProperties { private static boolean disableBuilder = true; - private static String adapterPackage = DEFAULT_BASE_PACKAGE; + private static String adapterPackage = ContextConstants.ConvertAdapter.packageName; + + private static String adapterClassName = ContextConstants.ConvertAdapter.convertMapperAdapterClassName; - private static String adapterClassName = DEFAULT_ADAPTER_CLASS_NAME; + private static String mapAdapterClassName = ContextConstants.ConvertAdapter.mapConvertMapperAdapterClassName; - private static String mapAdapterClassName = DEFAULT_MAP_ADAPTER_CLASS_NAME; + private static String autoConfigPackage = ContextConstants.AutoConfig.packageName; - private static String autoConfigPackage = DEFAULT_BASE_PACKAGE; + private static String autoMapperConfigClassName = ContextConstants.AutoConfig.autoMapperConfigClassName; - private static String autoMapperConfigClassName = AUTO_MAPPER_CONFIG_CLASS_NAME; + private static String autoMapMapperConfigClassName = ContextConstants.AutoConfig.autoMapMapperConfigClassName; - private static String autoMapMapperConfigClassName = AUTO_MAP_MAPPER_CONFIG_CLASS_NAME; + /* ******************** getter/setter ******************** */ public static String getMapperPackage() { return mapperPackage; @@ -53,6 +52,10 @@ public class AutoMapperProperties { return adapterClassName; } + public static String getCycleAvoidingAdapterClassName() { + return getAdapterClassName() + "ForCycleAvoiding"; + } + public static void setAdapterClassName(final String adapterClassName) { AutoMapperProperties.adapterClassName = adapterClassName; } @@ -77,6 +80,10 @@ public class AutoMapperProperties { return autoMapperConfigClassName; } + public static String getCycleAvoidingConfigClassName() { + return getConfigClassName() + "ForCycleAvoiding"; + } + public static void setAutoMapperConfigClassName(String autoMapperConfigClassName) { AutoMapperProperties.autoMapperConfigClassName = autoMapperConfigClassName; } 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 index 39f4a16dd261af4de5756c4684f4193e8c58773f..03c0cb52dc4bad4a3675cd5dffc141dfe0b367d8 100644 --- 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 @@ -1,7 +1,7 @@ package io.github.linpeilie.processor; -import cn.hutool.core.collection.CollectionUtil; -import cn.hutool.core.io.FileUtil; +import io.github.linpeilie.processor.utils.FileUtils; +import io.github.linpeilie.utils.CollectionUtils; import java.io.File; import java.io.IOException; import java.io.UncheckedIOException; @@ -38,10 +38,10 @@ public class BuildCollator { Filer filer = processingEnv.getFiler(); try { FileObject fileObject = filer.getResource(StandardLocation.CLASS_OUTPUT, "", - Constants.MAPSTRUCT_PLUS_META_INF + fileName); + ContextConstants.MetaInf.folder + fileName); this.collatorFile = new File(fileObject.getName()); if (collatorFile.exists()) { - records = FileUtil.readUtf8Lines(collatorFile) + records = FileUtils.readUtf8Lines(collatorFile) .stream() .map(line -> processingEnv.getElementUtils().getTypeElement(line)) .filter(Objects::nonNull) @@ -62,7 +62,7 @@ public class BuildCollator { } private void write(Collection lines) { - if (CollectionUtil.isEmpty(lines)) { + if (CollectionUtils.isEmpty(lines)) { return; } @@ -71,8 +71,8 @@ public class BuildCollator { .filter(Objects::nonNull) .collect(Collectors.toList()); - FileUtil.mkParentDirs(collatorFile); - FileUtil.writeUtf8Lines(lines, collatorFile); + FileUtils.mkParentDirs(collatorFile); + FileUtils.writeUtf8Lines(lines, collatorFile); } private List loadRecords() { @@ -87,7 +87,7 @@ public class BuildCollator { public void consumeRecords(Consumer consumer) { final List typeElements = getRecords(); - if (CollectionUtil.isNotEmpty(typeElements)) { + if (CollectionUtils.isNotEmpty(typeElements)) { typeElements.forEach(consumer); } } 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 deleted file mode 100644 index 58ee910b6942b7be40b60b593a7d9ddc93b72f78..0000000000000000000000000000000000000000 --- a/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/Constants.java +++ /dev/null @@ -1,52 +0,0 @@ -package io.github.linpeilie.processor; - -import java.io.File; -import org.mapstruct.MappingConstants; - -public class Constants { - - public static final String DEFAULT_BASE_PACKAGE = "io.github.linpeilie"; - - public static final String DEFAULT_COMPONENT_MODEL = MappingConstants.ComponentModel.SPRING; - - public static final String DEFAULT_ADAPTER_CLASS_NAME = "ConvertMapperAdapter"; - - public static final String DEFAULT_MAP_ADAPTER_CLASS_NAME = "MapConvertMapperAdapter"; - - public static final String AUTO_MAPPER_CONFIG_CLASS_NAME = "AutoMapperConfig"; - - public static final String AUTO_MAP_MAPPER_CONFIG_CLASS_NAME = "AutoMapMapperConfig"; - - public static final String AUTO_MAPPER_ANNOTATION = "io.github.linpeilie.annotations.AutoMapper"; - - public static final String AUTO_MAPPERS_ANNOTATION = "io.github.linpeilie.annotations.AutoMappers"; - - public static final String AUTO_MAP_MAPPER_ANNOTATION = "io.github.linpeilie.annotations.AutoMapMapper"; - - public static final String AUTO_ENUM_MAPPER_ANNOTATION = "io.github.linpeilie.annotations.AutoEnumMapper"; - - public static final String MAPPER_CONFIG_ANNOTATION = "io.github.linpeilie.annotations.MapperConfig"; - - public static final String MAPPER_ANNOTATION = "org.mapstruct.Mapper"; - - public static final String COMPONENT_MODEL_CONFIG_ANNOTATION = "io.github.linpeilie.annotations.ComponentModelConfig"; - - 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/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/ContextConstants.java b/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/ContextConstants.java new file mode 100644 index 0000000000000000000000000000000000000000..4c6fcc0b7be2c7ae874ca43c0d0b5a119e932291 --- /dev/null +++ b/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/ContextConstants.java @@ -0,0 +1,112 @@ +package io.github.linpeilie.processor; + +import org.mapstruct.MappingConstants; + +/** + * 上下文常量 + */ +public interface ContextConstants { + + /** + * BaseMapper 接口 + */ + interface BaseMapper { + String packageName = "io.github.linpeilie"; + String className = "BaseMapper"; + } + + /** + * BaseMapMapper 接口 + */ + interface BaseMapMapper { + String packageName = "io.github.linpeilie"; + String className = "BaseMapMapper"; + } + + /** + * BaseCycleAvoidingMapper 接口 + */ + interface BaseCycleAvoidingMapper { + String packageName = "io.github.linpeilie"; + String className = "BaseCycleAvoidingMapper"; + } + + interface Mapper { + String qualifiedClassName = "org.mapstruct.Mapper"; + String packageName = "org.mapstruct"; + String className = "Mapper"; + } + + interface AutoMapper { + String qualifiedClassName = "io.github.linpeilie.annotations.AutoMapper"; + } + + interface AutoMappers { + String qualifiedClassName = "io.github.linpeilie.annotations.AutoMappers"; + } + + interface AutoMapMapper { + String qualifiedClassName = "io.github.linpeilie.annotations.AutoMapMapper"; + } + + interface AutoEnumMapper { + String qualifiedClassName = "io.github.linpeilie.annotations.AutoEnumMapper"; + } + + interface MapperConfig { + String qualifiedClassName = "io.github.linpeilie.annotations.MapperConfig"; + } + + interface ComponentModelConfig { + String qualifiedClassName = "io.github.linpeilie.annotations.ComponentModelConfig"; + String defaultComponentModel = MappingConstants.ComponentModel.SPRING; + } + + interface ConvertAdapter { + String packageName = "io.github.linpeilie"; + String convertMapperAdapterClassName = "ConverterMapperAdapter"; + String mapConvertMapperAdapterClassName = "MapConvertMapperAdapter"; + } + + interface AutoConfig { + String packageName = "io.github.linpeilie"; + String autoMapperConfigClassName = "AutoMapperConfig"; + String autoMapMapperConfigClassName = "AutoMapMapperConfig"; + } + + interface Annotations { + String mapper = Mapper.qualifiedClassName; + String autoMapper = AutoMapper.qualifiedClassName; + String autoMappers = AutoMappers.qualifiedClassName; + String autoMapMapper = AutoMapMapper.qualifiedClassName; + String autoEnumMapper = AutoEnumMapper.qualifiedClassName; + String mapperConfig = MapperConfig.qualifiedClassName; + String componentModel = ComponentModelConfig.qualifiedClassName; + } + + interface MetaInf { + String folder = "META-INF/mps/"; + String mapperConfig = "config"; + String mappers = "mappers"; + String autoMapper = "autoMapper"; + String autoMappers = "autoMappers"; + String autoMapMappers = "autoMapMappers"; + String enumMappers = "enumMappers"; + } + + interface Map { + String packageName = "java.util"; + String className = "Map"; + } + + interface MapObjectConvert { + String packageName = "io.github.linpeilie.map"; + String className = "MapObjectConvert"; + } + + interface DoIgnore { + String packageName = "io.github.linpeilie.annotations"; + String className = "DoIgnore"; + } + +} 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 893054bf9628a0b1f7f21341210bf4f77b5479ee..2902e4ad863a5920cb7ce4241d9e7555f183c68b 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,7 +1,5 @@ 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; import com.squareup.javapoet.CodeBlock; @@ -10,14 +8,16 @@ import com.squareup.javapoet.MethodSpec; import com.squareup.javapoet.ParameterSpec; import com.squareup.javapoet.ParameterizedTypeName; import com.squareup.javapoet.TypeSpec; -import io.github.linpeilie.annotations.Immutable; +import io.github.linpeilie.processor.ContextConstants; import io.github.linpeilie.processor.metadata.AutoMapperMetadata; import io.github.linpeilie.processor.metadata.AutoMappingMetadata; +import io.github.linpeilie.utils.ArrayUtil; +import io.github.linpeilie.utils.CollectionUtils; +import io.github.linpeilie.utils.StrUtil; import java.io.IOException; import java.io.UncheckedIOException; import java.io.Writer; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Optional; @@ -26,10 +26,6 @@ import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.Modifier; import javax.lang.model.element.TypeElement; -import javax.tools.Diagnostic; -import org.apache.commons.lang3.StringUtils; - -import static io.github.linpeilie.processor.Constants.*; public class AutoMapperGenerator { @@ -40,8 +36,6 @@ public class AutoMapperGenerator { JavaFile.builder(metadata.mapperPackage(), createTypeSpec(processingEnv, metadata)).build().writeTo(writer); } catch (IOException e) { throw new UncheckedIOException(e); - } catch (Exception e) { - throw e; } } @@ -51,7 +45,6 @@ public class AutoMapperGenerator { final ClassName targetClassName = metadata.getTargetClassName(); - TypeSpec.Builder builder = TypeSpec.interfaceBuilder(metadata.mapperName()) .addSuperinterface(converterName) .addModifiers(Modifier.PUBLIC) @@ -61,32 +54,47 @@ public class AutoMapperGenerator { final ParameterSpec target = ParameterSpec.builder(targetClassName, "target") .addAnnotation(AnnotationSpec.builder(ClassName.get("org.mapstruct", "MappingTarget")).build()) .build(); + final ParameterSpec context = + ParameterSpec.builder(ClassName.get("io.github.linpeilie", "CycleAvoidingMappingContext"), "context") + .addAnnotation(ClassName.get("org.mapstruct", "Context")) + .build(); if (metadata.getFieldMappingList() != null && !metadata.getFieldMappingList().isEmpty()) { - builder.addMethod(addConvertMethodSpec(Collections.singletonList(source), metadata.getFieldMappingList(), - targetClassName)); + builder.addMethod(addConvertMethodSpec( + metadata.isCycleAvoiding() ? CollectionUtils.newArrayList(source, context) : Collections.singletonList(source), + metadata.getFieldMappingList(), + targetClassName, + CONVERT_METHOD_NAME)); } boolean targetIsImmutable = classIsImmutable(processingEnv, targetClassName); if (targetIsImmutable) { - builder.addMethod(addEmptyConvertMethodForImmutableEntity(source, target, targetClassName)); - } else { - builder.addMethod(addConvertMethodSpec(Arrays.asList(source, target), metadata.getFieldMappingList(), - targetClassName)); + builder.addMethod( + addEmptyConvertMethodForImmutableEntity( + metadata.isCycleAvoiding() ? CollectionUtils.newArrayList(source, target, + context) : CollectionUtils.newArrayList(source, target), + targetClassName, + CONVERT_METHOD_NAME)); + } else if (metadata.getFieldMappingList() != null && !metadata.getFieldMappingList().isEmpty()) { + builder.addMethod(addConvertMethodSpec( + metadata.isCycleAvoiding() ? CollectionUtils.newArrayList(source, target, + context) : CollectionUtils.newArrayList(source, target), + metadata.getFieldMappingList(), + targetClassName, + CONVERT_METHOD_NAME)); } return builder.build(); } - private MethodSpec addEmptyConvertMethodForImmutableEntity(ParameterSpec source, - ParameterSpec target, - ClassName targetClassName) { - return MethodSpec.methodBuilder(CONVERT_METHOD_NAME) + private MethodSpec addEmptyConvertMethodForImmutableEntity(List parameterSpecs, + ClassName targetClassName, + String methodName) { + MethodSpec.Builder builder = MethodSpec.methodBuilder(methodName) .addModifiers(Modifier.PUBLIC, Modifier.DEFAULT) - .addParameter(source) - .addParameter(target) + .addParameters(parameterSpecs) .returns(targetClassName) - .addCode("return target;") - .build(); + .addCode("return target;"); + return builder.build(); } private boolean classIsImmutable(ProcessingEnvironment processingEnv, ClassName className) { @@ -102,13 +110,14 @@ public class AutoMapperGenerator { } private MethodSpec addConvertMethodSpec(List parameterSpecs, - List autoMappingMetadataList, - ClassName target) { - final MethodSpec.Builder methodSpecBuilder = MethodSpec.methodBuilder(CONVERT_METHOD_NAME) + List autoMappingMetadataList, + ClassName target, String methodName) { + final MethodSpec.Builder methodSpecBuilder = MethodSpec.methodBuilder(methodName) .addParameters(parameterSpecs) .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT) + .addAnnotation(ClassName.get(ContextConstants.DoIgnore.packageName, ContextConstants.DoIgnore.className)) .returns(target); - if (CollectionUtil.isNotEmpty(autoMappingMetadataList)) { + if (CollectionUtils.isNotEmpty(autoMappingMetadataList)) { methodSpecBuilder.addAnnotations(buildMappingAnnotations(autoMappingMetadataList)); } return methodSpecBuilder.build(); @@ -131,17 +140,31 @@ public class AutoMapperGenerator { builder.addMember("defaultValue", CodeBlock.builder().add("$S", autoMappingMetadata.getDefaultValue()).build()); } - if (StringUtils.isNotEmpty(autoMappingMetadata.getExpression())) { + if (StrUtil.isNotEmpty(autoMappingMetadata.getExpression())) { builder.addMember("expression", CodeBlock.builder().add("$S", autoMappingMetadata.getExpression()).build()); } else { builder.addMember("source", CodeBlock.builder().add("$S", autoMappingMetadata.getSource()).build()); } - if (StringUtils.isNotEmpty(autoMappingMetadata.getDefaultExpression())) { - builder.addMember("defaultExpression",CodeBlock.builder().add("$S", autoMappingMetadata.getDefaultExpression()).build()); + if (StrUtil.isNotEmpty(autoMappingMetadata.getDefaultExpression())) { + builder.addMember("defaultExpression", + CodeBlock.builder().add("$S", autoMappingMetadata.getDefaultExpression()).build()); + } + if (StrUtil.isNotEmpty(autoMappingMetadata.getConditionExpression())) { + builder.addMember("conditionExpression", + CodeBlock.builder().add("$S", autoMappingMetadata.getConditionExpression()).build()); + } + if (ArrayUtil.isNotEmpty(autoMappingMetadata.getQualifiedByName())) { + builder.addMember("qualifiedByName", CodeBlock.builder().add("$L", + "{" + ArrayUtil.join(autoMappingMetadata.getQualifiedByName(), ",", "\"", "\"") + "}").build()); + } + if (ArrayUtil.isNotEmpty(autoMappingMetadata.getConditionQualifiedByName())) { + builder.addMember("conditionQualifiedByName", CodeBlock.builder().add("$L", + "{" + ArrayUtil.join(autoMappingMetadata.getConditionQualifiedByName(), ",", "\"", "\"") + "}").build()); } - if (StringUtils.isNotEmpty(autoMappingMetadata.getConditionExpression())) { - builder.addMember("conditionExpression", CodeBlock.builder().add("$S", autoMappingMetadata.getConditionExpression()).build()); + if (ArrayUtil.isNotEmpty(autoMappingMetadata.getDependsOn())) { + builder.addMember("dependsOn", CodeBlock.builder().add("$L", + "{" + ArrayUtil.join(autoMappingMetadata.getDependsOn(), ",", "\"", "\"") + "}").build()); } return builder.build(); }).collect(Collectors.toList()); @@ -180,7 +203,8 @@ public class AutoMapperGenerator { final CodeBlock importsCodeBlock = importsCodeBuilder.add("}").build(); AnnotationSpec.Builder builder = - AnnotationSpec.builder(ClassName.get(MAPSTRUCT_MAPPER_PACKAGE, MAPSTRUCT_MAPPER_CLASS_NAME)) + AnnotationSpec.builder( + ClassName.get(ContextConstants.Mapper.packageName, ContextConstants.Mapper.className)) .addMember("config", configCodeBlock) .addMember("uses", usesCodeBlock) .addMember("imports", importsCodeBlock); diff --git a/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/generator/DefaultAdapterMapperGenerator.java b/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/generator/DefaultAdapterMapperGenerator.java index 3caaa0f46e035db3375c3ed86e04f30cb3a0a03d..67c604c6836225b9aa4395db9b3363d7e1ed9185 100644 --- a/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/generator/DefaultAdapterMapperGenerator.java +++ b/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/generator/DefaultAdapterMapperGenerator.java @@ -12,24 +12,17 @@ import javax.lang.model.element.Modifier; public class DefaultAdapterMapperGenerator extends AbstractAdapterMapperGenerator { - public TypeSpec createTypeSpec(Collection adapterMethods, String adapterClassName) { - TypeSpec.Builder adapterBuilder = TypeSpec.classBuilder( - ClassName.get(adapterPackage(), adapterClassName)) - .addModifiers(Modifier.PUBLIC); - - adapterMethods.forEach(adapterMethod -> adapterBuilder.addMethod(buildProxyMethod(adapterMethod))); - - return adapterBuilder.build(); - } - - private String firstWordToLower(String str) { - return str.substring(0, 1).toLowerCase() + str.substring(1); - } - @Override protected CodeBlock proxyMethodTarget(final AbstractAdapterMethodMetadata adapterMethodMetadata) { return CodeBlock.of("return ($T.getMapper($T.class)).$N($N);", ClassName.get("org.mapstruct.factory", "Mappers"), adapterMethodMetadata.getMapper(), - adapterMethodMetadata.getMapperMethodName(), "param"); + adapterMethodMetadata.getMapperMethodName(), PARAM__PARAMETER_NAME); + } + + @Override + protected CodeBlock cycleAvoidingMethodTarget(AbstractAdapterMethodMetadata adapterMethodMetadata) { + return CodeBlock.of("return ($T.getMapper($T.class)).$N($N, $N);", + ClassName.get("org.mapstruct.factory", "Mappers"), adapterMethodMetadata.getMapper(), + adapterMethodMetadata.cycleAvoidingMethodName(), PARAM__PARAMETER_NAME, CONTEXT__PARAMETER_NAME); } } diff --git a/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/generator/IocAdapterMapperGenerator.java b/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/generator/IocAdapterMapperGenerator.java index 0de2aadb7fc30241dfe20c843417d267ca0e42bc..e6d49303a7a28fdd793d81f5552aa258b45f0a1b 100644 --- a/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/generator/IocAdapterMapperGenerator.java +++ b/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/generator/IocAdapterMapperGenerator.java @@ -4,40 +4,44 @@ import com.squareup.javapoet.AnnotationSpec; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.CodeBlock; import com.squareup.javapoet.FieldSpec; +import com.squareup.javapoet.MethodSpec; import com.squareup.javapoet.TypeSpec; import io.github.linpeilie.processor.AbstractAdapterMapperGenerator; import io.github.linpeilie.processor.metadata.AbstractAdapterMethodMetadata; -import java.util.Collection; import java.util.List; import javax.lang.model.element.Modifier; public abstract class IocAdapterMapperGenerator extends AbstractAdapterMapperGenerator { + protected static final String CONVERTER_FIELD_NAME = "converter"; + protected abstract AnnotationSpec componentAnnotation(); protected abstract List injectAnnotations(); @Override - protected TypeSpec createTypeSpec(final Collection adapterMethods, - final String adapterClassName) { + protected TypeSpec createTypeSpec(List methods, String adapterClassName, ClassName superClass) { TypeSpec.Builder adapterBuilder = TypeSpec.classBuilder(ClassName.get(adapterPackage(), adapterClassName)) .addModifiers(Modifier.PUBLIC) .addAnnotation(componentAnnotation()); - adapterMethods.stream() - .filter(adapterMethodMetadata -> !adapterMethodMetadata.isStatic()) - .map(AbstractAdapterMethodMetadata::getMapper) - .distinct() - .forEach(mapper -> adapterBuilder.addField(buildMapperField(mapper))); + adapterBuilder.addField(buildConverterField()); + + adapterBuilder.addMethods(methods); - adapterMethods.forEach(adapterMethod -> adapterBuilder - .addMethod(buildProxyMethod(adapterMethod))); + if (superClass != null) { + adapterBuilder.superclass(superClass); + } return adapterBuilder.build(); } - private FieldSpec buildMapperField(ClassName mapper) { - return FieldSpec.builder(mapper, firstWordToLower(mapper.simpleName()), Modifier.PRIVATE) + private FieldSpec buildConverterField() { + return FieldSpec.builder( + ClassName.get("io.github.linpeilie", "Converter"), + CONVERTER_FIELD_NAME, + Modifier.PRIVATE + ) .addAnnotations(injectAnnotations()) .build(); } @@ -46,13 +50,33 @@ public abstract class IocAdapterMapperGenerator extends AbstractAdapterMapperGen return str.substring(0, 1).toLowerCase() + str.substring(1); } + /** + * + * return converter.convert(param, Target.class); + * + */ @Override protected CodeBlock proxyMethodTarget(AbstractAdapterMethodMetadata adapterMethodMetadata) { return CodeBlock.builder() - .add("return $N.$N($N);", firstWordToLower(adapterMethodMetadata.getMapper().simpleName()), - adapterMethodMetadata.getMapperMethodName(), - "param") + .add("return $N.convert($N, $T.class);\n", CONVERTER_FIELD_NAME, + PARAM__PARAMETER_NAME, + adapterMethodMetadata.getReturn()) .build(); } + /** + * + * return converter.convert(param, Target.class, context); + * + */ + @Override + protected CodeBlock cycleAvoidingMethodTarget(AbstractAdapterMethodMetadata adapterMethodMetadata) { + return CodeBlock.builder() + .add("return $N.convert($N, $T.class, $N);\n", + CONVERTER_FIELD_NAME, + PARAM__PARAMETER_NAME, + adapterMethodMetadata.getReturn(), + CONTEXT__PARAMETER_NAME) + .build(); + } } 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 0e086f3862cc030a5cd00c9e04418ace0b922182..8dc5a9a199c328e738f715271cd82268dd050f9c 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 @@ -1,18 +1,17 @@ package io.github.linpeilie.processor.generator; -import cn.hutool.core.collection.CollectionUtil; import com.squareup.javapoet.AnnotationSpec; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.CodeBlock; import com.squareup.javapoet.JavaFile; import com.squareup.javapoet.TypeSpec; import io.github.linpeilie.processor.AutoMapperProperties; +import io.github.linpeilie.utils.CollectionUtils; import java.io.IOException; import java.io.Writer; import java.util.List; import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.Modifier; -import javax.lang.model.element.TypeElement; import javax.lang.model.type.TypeMirror; import static javax.tools.Diagnostic.Kind.ERROR; @@ -44,7 +43,7 @@ public class MapperConfigGenerator { private AnnotationSpec buildMapperConfigAnnotationSpec(final String adapterClassName, final List uses) { CodeBlock.Builder usesCodeBuilder = CodeBlock.builder().add("{"); usesCodeBuilder.add("$T.class", ClassName.get(AutoMapperProperties.getAdapterPackage(), adapterClassName)); - if (CollectionUtil.isNotEmpty(uses)) { + if (CollectionUtils.isNotEmpty(uses)) { uses.forEach(use -> { usesCodeBuilder.add(", $T.class", use); }); diff --git a/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/generator/SolonAdapterMapperGenerator.java b/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/generator/SolonAdapterMapperGenerator.java index 87c4447186f0035008c1da5cfe29fd852818a871..bbc26bf3e19f70c7405973b09f9dd9ba15e2735e 100644 --- a/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/generator/SolonAdapterMapperGenerator.java +++ b/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/generator/SolonAdapterMapperGenerator.java @@ -1,8 +1,8 @@ package io.github.linpeilie.processor.generator; -import cn.hutool.core.collection.CollectionUtil; import com.squareup.javapoet.AnnotationSpec; import com.squareup.javapoet.ClassName; +import io.github.linpeilie.utils.CollectionUtils; import java.util.Collection; import java.util.List; @@ -25,6 +25,6 @@ public class SolonAdapterMapperGenerator extends IocAdapterMapperGenerator { @Override protected List injectAnnotations() { - return CollectionUtil.newArrayList(inject()); + return CollectionUtils.newArrayList(inject()); } } diff --git a/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/generator/SpringAdapterMapperGenerator.java b/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/generator/SpringAdapterMapperGenerator.java index 6dbda0dc1078b8a1ee3f71300e0534207e9991b7..e8f3337e5f974006ae8818d3d75db77a2ca04035 100644 --- a/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/generator/SpringAdapterMapperGenerator.java +++ b/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/generator/SpringAdapterMapperGenerator.java @@ -1,18 +1,9 @@ package io.github.linpeilie.processor.generator; -import cn.hutool.core.collection.CollectionUtil; import com.squareup.javapoet.AnnotationSpec; import com.squareup.javapoet.ClassName; -import com.squareup.javapoet.CodeBlock; -import com.squareup.javapoet.FieldSpec; -import com.squareup.javapoet.MethodSpec; -import com.squareup.javapoet.ParameterSpec; -import com.squareup.javapoet.TypeSpec; -import io.github.linpeilie.processor.AbstractAdapterMapperGenerator; -import io.github.linpeilie.processor.metadata.AbstractAdapterMethodMetadata; -import java.util.Collection; +import io.github.linpeilie.utils.CollectionUtils; import java.util.List; -import javax.lang.model.element.Modifier; public class SpringAdapterMapperGenerator extends IocAdapterMapperGenerator { @@ -28,12 +19,6 @@ public class SpringAdapterMapperGenerator extends IocAdapterMapperGenerator { .build(); } - private AnnotationSpec lazy() { - return AnnotationSpec - .builder(ClassName.get("org.springframework.context.annotation", "Lazy")) - .build(); - } - @Override protected AnnotationSpec componentAnnotation() { return component(); @@ -41,7 +26,7 @@ public class SpringAdapterMapperGenerator extends IocAdapterMapperGenerator { @Override protected List injectAnnotations() { - return CollectionUtil.newArrayList(autowired(), lazy()); + return CollectionUtils.newArrayList(autowired()); } } diff --git a/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/metadata/AbstractAdapterMethodMetadata.java b/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/metadata/AbstractAdapterMethodMetadata.java index 1c46378055f430a9cabe65b2f571f539f2de9089..ec5de7a3851bbd266af054e1c60d8726f86aacf8 100644 --- a/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/metadata/AbstractAdapterMethodMetadata.java +++ b/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/metadata/AbstractAdapterMethodMetadata.java @@ -32,4 +32,12 @@ public abstract class AbstractAdapterMethodMetadata { return false; } + public boolean needCycleAvoiding() { + return false; + } + + public String cycleAvoidingMethodName() { + return "convertWithCycle"; + } + } diff --git a/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/metadata/AbstractMapperMetadata.java b/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/metadata/AbstractMapperMetadata.java index 4225adb547f80c7eafa9d39a1d4834c73f50ce34..1ef5515a61a44168d08219553ee836600839c3e2 100644 --- a/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/metadata/AbstractMapperMetadata.java +++ b/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/metadata/AbstractMapperMetadata.java @@ -2,14 +2,14 @@ package io.github.linpeilie.processor.metadata; import com.squareup.javapoet.ClassName; import io.github.linpeilie.processor.AutoMapperProperties; -import org.apache.commons.lang3.StringUtils; +import io.github.linpeilie.utils.StrUtil; public abstract class AbstractMapperMetadata { protected ClassName sourceClassName; public String mapperPackage() { - return StringUtils.isNotEmpty(AutoMapperProperties.getMapperPackage()) + return StrUtil.isNotEmpty(AutoMapperProperties.getMapperPackage()) ? AutoMapperProperties.getMapperPackage() : sourceClassName.packageName(); } diff --git a/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/metadata/AdapterMethodMetadata.java b/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/metadata/AdapterMethodMetadata.java index 83f0a2cebf8e87fd475e97a4fd9cc877bb9b955b..bd6f9e598c9a7fa78fdfdc43d6974fa905c24ef5 100644 --- a/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/metadata/AdapterMethodMetadata.java +++ b/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/metadata/AdapterMethodMetadata.java @@ -1,25 +1,28 @@ package io.github.linpeilie.processor.metadata; import com.squareup.javapoet.ClassName; +import io.github.linpeilie.processor.utils.ClassUtil; public class AdapterMethodMetadata extends AbstractAdapterMethodMetadata { - private AdapterMethodMetadata(final ClassName source, final ClassName target, ClassName mapper) { + private AdapterMethodMetadata(final ClassName source, final ClassName target, ClassName mapper, boolean needCycleAvoiding) { super(source, mapper); this.target = target; + this.needCycleAvoiding = needCycleAvoiding; } private final ClassName target; + private final boolean needCycleAvoiding; - public static AdapterMethodMetadata newInstance(ClassName source, ClassName target, ClassName mapper) { - return new AdapterMethodMetadata(source, target, mapper); + public static AdapterMethodMetadata newInstance(ClassName source, ClassName target, ClassName mapper, boolean needCycleAvoiding) { + return new AdapterMethodMetadata(source, target, mapper, needCycleAvoiding); } @Override public String getMethodName() { - final String sourceName = source.toString().replace(".", "_"); - return sourceName.substring(0, 1).toLowerCase() + sourceName.substring(1) + "To" + - target.simpleName(); + String source = ClassUtil.simplifyQualifiedName(this.source.toString()); + source = source.substring(0, 1).toLowerCase() + source.substring(1); + return source + "To" + target.simpleName(); } public ClassName getTarget() { @@ -35,4 +38,9 @@ public class AdapterMethodMetadata extends AbstractAdapterMethodMetadata { public String getMapperMethodName() { return "convert"; } + + @Override + public boolean needCycleAvoiding() { + return needCycleAvoiding; + } } diff --git a/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/metadata/AutoMapMapperMetadata.java b/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/metadata/AutoMapMapperMetadata.java index b22e27c9e6c99aa12708070b50cf8b27bbf24c79..5438689ed026f61f5acd1d6947aed26957700e55 100644 --- a/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/metadata/AutoMapMapperMetadata.java +++ b/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/metadata/AutoMapMapperMetadata.java @@ -1,13 +1,13 @@ package io.github.linpeilie.processor.metadata; import io.github.linpeilie.processor.AutoMapperProperties; -import org.apache.commons.lang3.StringUtils; +import io.github.linpeilie.utils.StrUtil; public class AutoMapMapperMetadata extends AutoMapperMetadata { @Override public String mapperPackage() { - return StringUtils.isNotEmpty(AutoMapperProperties.getMapperPackage()) - ? AutoMapperProperties.getMapperPackage() : getTargetClassName().packageName(); + return StrUtil.isNotEmpty( + AutoMapperProperties.getMapperPackage()) ? AutoMapperProperties.getMapperPackage() : getTargetClassName().packageName(); } } diff --git a/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/metadata/AutoMapperMetadata.java b/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/metadata/AutoMapperMetadata.java index 6f4d58ba5cba660caea27f0499e14dc958448ac9..30ace3e507466b9999ba7bf91db4899958abb7f5 100644 --- a/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/metadata/AutoMapperMetadata.java +++ b/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/metadata/AutoMapperMetadata.java @@ -1,9 +1,7 @@ package io.github.linpeilie.processor.metadata; import com.squareup.javapoet.ClassName; -import io.github.linpeilie.processor.AutoMapperProperties; import java.util.List; -import org.mapstruct.ReportingPolicy; public class AutoMapperMetadata extends AbstractMapperMetadata { @@ -27,6 +25,8 @@ public class AutoMapperMetadata extends AbstractMapperMetadata { private boolean reverseConvertGenerate; + private boolean cycleAvoiding; + public String mapperName() { return sourceClassName.simpleName() + "To" + targetClassName.simpleName() + "Mapper"; } @@ -113,4 +113,12 @@ public class AutoMapperMetadata extends AbstractMapperMetadata { public void setConvertGenerate(final boolean convertGenerate) { this.convertGenerate = convertGenerate; } + + public boolean isCycleAvoiding() { + return cycleAvoiding; + } + + public void setCycleAvoiding(boolean cycleAvoiding) { + this.cycleAvoiding = cycleAvoiding; + } } diff --git a/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/metadata/AutoMappingMetadata.java b/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/metadata/AutoMappingMetadata.java index 6ce2531922281b19af3f2a0d4bd95d27a3131e39..dbf89448752a2c20daf0d38d88dc1d7966abcc4b 100644 --- a/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/metadata/AutoMappingMetadata.java +++ b/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/metadata/AutoMappingMetadata.java @@ -24,6 +24,12 @@ public class AutoMappingMetadata { private String defaultValue = ""; + String[] qualifiedByName = {}; + + String[] conditionQualifiedByName = {}; + + String[] dependsOn = {}; + public ClassName getTargetClass() { return targetClass; } @@ -103,4 +109,28 @@ public class AutoMappingMetadata { public void setConditionExpression(final String conditionExpression) { this.conditionExpression = conditionExpression; } + + public String[] getQualifiedByName() { + return qualifiedByName; + } + + public void setQualifiedByName(String[] qualifiedByName) { + this.qualifiedByName = qualifiedByName; + } + + public String[] getConditionQualifiedByName() { + return conditionQualifiedByName; + } + + public void setConditionQualifiedByName(String[] conditionQualifiedByName) { + this.conditionQualifiedByName = conditionQualifiedByName; + } + + public String[] getDependsOn() { + return dependsOn; + } + + public void setDependsOn(String[] dependsOn) { + this.dependsOn = dependsOn; + } } diff --git a/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/solon/SolonComponentProcessor.java b/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/solon/SolonComponentProcessor.java index 168668d5349181f1d24d1bb6ecd3c7247d3eb111..4c6aa638c2b26b33ab21d21ba999125da6549255 100644 --- a/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/solon/SolonComponentProcessor.java +++ b/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/solon/SolonComponentProcessor.java @@ -1,8 +1,7 @@ package io.github.linpeilie.processor.solon; -import cn.hutool.core.collection.CollectionUtil; -import cn.hutool.core.collection.ListUtil; import io.github.linpeilie.ComponentModelConstant; +import io.github.linpeilie.utils.CollectionUtils; import java.util.List; import org.mapstruct.ap.internal.model.Annotation; import org.mapstruct.ap.internal.model.Mapper; @@ -16,7 +15,7 @@ public class SolonComponentProcessor extends AnnotationBasedComponentModelProces @Override protected List getTypeAnnotations(final Mapper mapper) { - return CollectionUtil.newArrayList(component()); + return CollectionUtils.newArrayList(component()); } private Annotation component() { @@ -29,7 +28,7 @@ public class SolonComponentProcessor extends AnnotationBasedComponentModelProces @Override protected List getMapperReferenceAnnotations() { - return CollectionUtil.newArrayList(inject()); + return CollectionUtils.newArrayList(inject()); } @Override diff --git a/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/utils/ClassUtil.java b/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/utils/ClassUtil.java new file mode 100644 index 0000000000000000000000000000000000000000..7aa0d2bacfe84f94dec179aa0c55ac4b0dc8d4e7 --- /dev/null +++ b/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/utils/ClassUtil.java @@ -0,0 +1,24 @@ +package io.github.linpeilie.processor.utils; + +public class ClassUtil { + + /** + * 简化类全限定名 + * @param qualifiedName + * @return + */ + public static String simplifyQualifiedName(String qualifiedName) { + String[] arr = qualifiedName.split("\\."); + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < arr.length; i++) { + if (i == arr.length - 1) { + sb.append("_"); + sb.append(arr[i]); + } else { + sb.append(arr[i].charAt(0)); + } + } + return sb.toString(); + } + +} diff --git a/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/utils/ExceptionUtils.java b/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/utils/ExceptionUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..3573c677cfe1f6f34652f7ca2f6f44ad1e5bbfaf --- /dev/null +++ b/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/utils/ExceptionUtils.java @@ -0,0 +1,15 @@ +package io.github.linpeilie.processor.utils; + +import java.io.PrintWriter; +import java.io.StringWriter; + +public class ExceptionUtils { + + public static String getStackTrace(final Throwable throwable) { + final StringWriter sw = new StringWriter(); + final PrintWriter pw = new PrintWriter(sw, true); + throwable.printStackTrace(pw); + return sw.getBuffer().toString(); + } + +} diff --git a/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/utils/FileReader.java b/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/utils/FileReader.java new file mode 100644 index 0000000000000000000000000000000000000000..c4d71b22c66819304f8d64f7b6c0703ca7170a8b --- /dev/null +++ b/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/utils/FileReader.java @@ -0,0 +1,61 @@ +package io.github.linpeilie.processor.utils; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.charset.Charset; +import java.util.Collection; + +public class FileReader extends FileWrapper { + private static final long serialVersionUID = 1L; + + public static FileReader create(File file, Charset charset){ + return new FileReader(file, charset); + } + + // ------------------------------------------------------- Constructor start + public FileReader(File file, Charset charset) { + super(file, charset); + checkFile(); + } + + // ------------------------------------------------------- Constructor end + + public > T readLines(T collection) { + BufferedReader reader = null; + try { + reader = FileUtils.getReader(file, charset); + String line; + while (true) { + line = reader.readLine(); + if (line == null) { + break; + } + collection.add(line); + } + return collection; + } catch (IOException e) { + throw new UncheckedIOException(e); + } finally { + if (null != reader) { + try { + reader.close(); + } catch (Exception e) { + // 静默关闭 + } + } + } + } + + private void checkFile() { + if (file == null) { + throw new UncheckedIOException(new IOException("File to write content is null !")); + } + if (this.file.exists() && !file.isFile()) { + throw new UncheckedIOException( + new IOException("File [" + this.file.getAbsoluteFile() + "] is not a file !")); + } + } + +} diff --git a/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/utils/FileUtils.java b/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/utils/FileUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..6ed0266c8a46921344b598a4873d55ccf6abce29 --- /dev/null +++ b/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/utils/FileUtils.java @@ -0,0 +1,145 @@ +package io.github.linpeilie.processor.utils; + +import java.io.BufferedInputStream; +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.UncheckedIOException; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +public class FileUtils { + + public static File writeUtf8Lines(Collection list, File file) { + return writeLines(list, file, StandardCharsets.UTF_8); + } + + public static File writeLines(Collection list, File file, Charset charset) { + return writeLines(list, file, charset, false); + } + + public static File writeLines(Collection list, File file, Charset charset, boolean isAppend) { + return FileWriter.create(file, charset).writeLines(list, isAppend); + } + + public static List readUtf8Lines(File file) { + return readLines(file, StandardCharsets.UTF_8); + } + + public static List readLines(File file, Charset charset) { + return readLines(file, charset, new ArrayList<>()); + } + + public static > T readLines(File file, Charset charset, T collection) { + return FileReader.create(file, charset).readLines(collection); + } + + public static BufferedInputStream getInputStream(File file) throws IOException { + return new BufferedInputStream(Files.newInputStream(file.toPath())); + } + + public static BufferedReader getReader(File file, Charset charset) throws IOException { + BufferedInputStream in = getInputStream(file); + InputStreamReader reader; + if (null == charset) { + reader = new InputStreamReader(in); + } else { + reader = new InputStreamReader(in, charset); + } + + return new BufferedReader(reader); + } + + public static File mkParentDirs(File file) { + if (null == file) { + return null; + } + return mkdir(getParent(file, 1)); + } + + public static File getParent(File file, int level) { + if (level < 1 || null == file) { + return file; + } + + File parentFile; + try { + parentFile = file.getCanonicalFile().getParentFile(); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + if (1 == level) { + return parentFile; + } + return getParent(parentFile, level - 1); + } + + public static File mkdir(File dir) { + if (dir == null) { + return null; + } + if (false == dir.exists()) { + mkdirsSafely(dir, 5, 1); + } + return dir; + } + + public static boolean mkdirsSafely(File dir, int tryCount, long sleepMillis) { + if (dir == null) { + return false; + } + if (dir.isDirectory()) { + return true; + } + for (int i = 1; i <= tryCount; i++) { // 高并发场景下,可以看到 i 处于 1 ~ 3 之间 + // 如果文件已存在,也会返回 false,所以该值不能作为是否能创建的依据,因此不对其进行处理 + //noinspection ResultOfMethodCallIgnored + dir.mkdirs(); + if (dir.exists()) { + return true; + } + ThreadUtils.sleep(sleepMillis); + } + return dir.exists(); + } + + public static File touch(File file) { + if (null == file) { + return null; + } + if (false == file.exists()) { + mkParentDirs(file); + try { + //noinspection ResultOfMethodCallIgnored + file.createNewFile(); + } catch (Exception e) { + throw new UncheckedIOException(new IOException(e)); + } + } + return file; + } + + public static boolean isNotEmpty(File file) { + return !isEmpty(file); + } + + public static boolean isEmpty(File file) { + if (null == file || !file.exists()) { + return true; + } + + if (file.isDirectory()) { + String[] subFiles = file.list(); + return subFiles == null || subFiles.length == 0; + } else if (file.isFile()) { + return file.length() <= 0; + } + + return false; + } +} diff --git a/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/utils/FileWrapper.java b/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/utils/FileWrapper.java new file mode 100644 index 0000000000000000000000000000000000000000..d26f74003be3a8f79db90976b61ea9248324b98e --- /dev/null +++ b/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/utils/FileWrapper.java @@ -0,0 +1,68 @@ +package io.github.linpeilie.processor.utils; + +import java.io.File; +import java.io.Serializable; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; + +/** + * 文件包装器,扩展文件对象 + * + * @author Looly + * + */ +public class FileWrapper implements Serializable { + private static final long serialVersionUID = 1L; + + protected File file; + protected Charset charset; + + /** 默认编码:UTF-8 */ + public static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8; + + // ------------------------------------------------------- Constructor start + public FileWrapper(File file, Charset charset) { + this.file = file; + this.charset = charset; + } + // ------------------------------------------------------- Constructor end + + // ------------------------------------------------------- Setters and Getters start start + /** + * 获得文件 + * @return 文件 + */ + public File getFile() { + return file; + } + + /** + * 设置文件 + * @param file 文件 + * @return 自身 + */ + public FileWrapper setFile(File file) { + this.file = file; + return this; + } + + /** + * 获得字符集编码 + * @return 编码 + */ + public Charset getCharset() { + return charset; + } + + /** + * 设置字符集编码 + * @param charset 编码 + * @return 自身 + */ + public FileWrapper setCharset(Charset charset) { + this.charset = charset; + return this; + } + // ------------------------------------------------------- Setters and Getters start end + +} diff --git a/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/utils/FileWriter.java b/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/utils/FileWriter.java new file mode 100644 index 0000000000000000000000000000000000000000..4d3691ec7c833eea4766a3db13ebbf6c3b849f49 --- /dev/null +++ b/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/utils/FileWriter.java @@ -0,0 +1,89 @@ +package io.github.linpeilie.processor.utils; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.io.UncheckedIOException; +import java.nio.charset.Charset; + +public class FileWriter extends FileWrapper { + + private static final long serialVersionUID = 1L; + + public static FileWriter create(File file, Charset charset) { + return new FileWriter(file, charset); + } + + // ------------------------------------------------------- Constructor start + + public FileWriter(File file, Charset charset) { + super(file, charset); + checkFile(); + } + + // ------------------------------------------------------- Constructor end + + public File writeLines(Iterable list, boolean isAppend) { + return writeLines(list, null, isAppend); + } + + public BufferedWriter getWriter(boolean isAppend) { + try { + return new BufferedWriter( + new OutputStreamWriter(new FileOutputStream(FileUtils.touch(file), isAppend), charset)); + } catch (Exception e) { + throw new UncheckedIOException(new IOException(e)); + } + } + + public PrintWriter getPrintWriter(boolean isAppend) { + return new PrintWriter(getWriter(isAppend)); + } + + public File writeLines(Iterable list, LineSeparator lineSeparator, boolean isAppend) { + try (PrintWriter writer = getPrintWriter(isAppend)) { + boolean isFirst = true; + for (T t : list) { + if (null != t) { + if (isFirst) { + isFirst = false; + if (isAppend && FileUtils.isNotEmpty(this.file)) { + // 追加模式下且文件非空,补充换行符 + printNewLine(writer, lineSeparator); + } + } else { + printNewLine(writer, lineSeparator); + } + writer.print(t); + + writer.flush(); + } + } + } + return this.file; + } + + private void printNewLine(PrintWriter writer, LineSeparator lineSeparator) { + if (null == lineSeparator) { + //默认换行符 + writer.println(); + } else { + //自定义换行符 + writer.print(lineSeparator.getValue()); + } + } + + private void checkFile() { + if (file == null) { + throw new UncheckedIOException(new IOException("File to write content is null !")); + } + if (this.file.exists() && !file.isFile()) { + throw new UncheckedIOException( + new IOException("File [" + this.file.getAbsoluteFile() + "] is not a file !")); + } + } + +} diff --git a/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/utils/LineSeparator.java b/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/utils/LineSeparator.java new file mode 100644 index 0000000000000000000000000000000000000000..e7cb19894060f0f7f3f82e67eed82b1adbe40341 --- /dev/null +++ b/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/utils/LineSeparator.java @@ -0,0 +1,20 @@ +package io.github.linpeilie.processor.utils; + +public enum LineSeparator { + /** Mac系统换行符:"\r" */ + MAC("\r"), + /** Linux系统换行符:"\n" */ + LINUX("\n"), + /** Windows系统换行符:"\r\n" */ + WINDOWS("\r\n"); + + private final String value; + + LineSeparator(String lineSeparator) { + this.value = lineSeparator; + } + + public String getValue() { + return this.value; + } +} diff --git a/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/utils/ObjectUtils.java b/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/utils/ObjectUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..db76207db6a3d6c26e811ba6a6af23e39d16b77f --- /dev/null +++ b/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/utils/ObjectUtils.java @@ -0,0 +1,9 @@ +package io.github.linpeilie.processor.utils; + +public class ObjectUtils { + + public static T defaultIfNull(final T object, final T defaultValue) { + return object != null ? object : defaultValue; + } + +} diff --git a/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/utils/ThreadUtils.java b/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/utils/ThreadUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..0dcefc9c8b72a7715aacd842cacd6ca548ee4ecc --- /dev/null +++ b/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/utils/ThreadUtils.java @@ -0,0 +1,14 @@ +package io.github.linpeilie.processor.utils; + +public class ThreadUtils { + public static boolean sleep(long millis) { + if (millis > 0) { + try { + Thread.sleep(millis); + } catch (InterruptedException e) { + return false; + } + } + return true; + } +} diff --git a/mapstruct-plus-spring-boot-starter/src/main/java/io/github/linpeilie/mapstruct/SpringConverterFactory.java b/mapstruct-plus-spring-boot-starter/src/main/java/io/github/linpeilie/mapstruct/SpringConverterFactory.java index e3e91e786f63c5bd5178909ba2de98c725b1768a..28330d192a8d8f2953a62c160ce0bfff719f6a11 100644 --- a/mapstruct-plus-spring-boot-starter/src/main/java/io/github/linpeilie/mapstruct/SpringConverterFactory.java +++ b/mapstruct-plus-spring-boot-starter/src/main/java/io/github/linpeilie/mapstruct/SpringConverterFactory.java @@ -1,6 +1,7 @@ package io.github.linpeilie.mapstruct; import io.github.linpeilie.AbstractCachedConverterFactory; +import io.github.linpeilie.BaseCycleAvoidingMapper; import io.github.linpeilie.BaseMapMapper; import io.github.linpeilie.BaseMapper; import org.springframework.context.ApplicationContext; diff --git a/mapstruct-plus/pom.xml b/mapstruct-plus/pom.xml index cca4476b3f9da65ed73589a5ec998379372e2a33..faa09f679a9065634bbbdce2ade43a4e2aaae2cc 100644 --- a/mapstruct-plus/pom.xml +++ b/mapstruct-plus/pom.xml @@ -24,8 +24,8 @@ mapstruct - cn.hutool - hutool-core + io.github.linpeilie + mapstruct-plus-object-convert 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 84c82ddb2d69147895593e7ae278a0cab886d04a..0b0e15b8baf2fbe044daff2016fbaca5c281b570 100644 --- a/mapstruct-plus/src/main/java/io/github/linpeilie/AbstractCachedConverterFactory.java +++ b/mapstruct-plus/src/main/java/io/github/linpeilie/AbstractCachedConverterFactory.java @@ -6,18 +6,20 @@ public abstract class AbstractCachedConverterFactory implements ConverterFactory private final ConcurrentHashMap mapperMap = new ConcurrentHashMap<>(); + private final ConcurrentHashMap cycleAvoidingMapper = new ConcurrentHashMap<>(); + private final ConcurrentHashMap mapMapperMap = new ConcurrentHashMap<>(); @Override @SuppressWarnings("unchecked") public BaseMapper getMapper(final Class sourceType, final Class targetType) { - final Class source = wrapperClass(sourceType); - final Class target = wrapperClass(targetType); - final String key = key(source, target); + Class source = wrapperClass(sourceType); + Class target = wrapperClass(targetType); + String key = key(source, target); if (mapperMap.containsKey(key)) { return mapperMap.get(key); } - final BaseMapper mapper = findMapper(source, target); + BaseMapper mapper = findMapper(source, target); if (mapper != null) { mapperMap.put(key, mapper); return mapper; @@ -25,6 +27,18 @@ public abstract class AbstractCachedConverterFactory implements ConverterFactory return null; } + @Override + public BaseCycleAvoidingMapper getCycleAvoidingMapper(Class sourceType, Class targetType) { + BaseMapper mapper = getMapper(sourceType, targetType); + if (mapper == null) { + return null; + } + if (mapper instanceof BaseCycleAvoidingMapper) { + return (BaseCycleAvoidingMapper) mapper; + } + return null; + } + @Override public BaseMapMapper getMapMapper(final Class sourceType) { final Class source = wrapperClass(sourceType); @@ -44,7 +58,7 @@ public abstract class AbstractCachedConverterFactory implements ConverterFactory return clazz; } - protected abstract BaseMapper findMapper(final Class source, final Class target); + protected abstract BaseMapper findMapper(Class source, Class target); protected abstract BaseMapMapper findMapMapper(final Class source); diff --git a/mapstruct-plus/src/main/java/io/github/linpeilie/BaseCycleAvoidingMapper.java b/mapstruct-plus/src/main/java/io/github/linpeilie/BaseCycleAvoidingMapper.java new file mode 100644 index 0000000000000000000000000000000000000000..75ba1f62f14766d05ae0e1ac7546fbeed46f1966 --- /dev/null +++ b/mapstruct-plus/src/main/java/io/github/linpeilie/BaseCycleAvoidingMapper.java @@ -0,0 +1,43 @@ +package io.github.linpeilie; + +import io.github.linpeilie.annotations.DoIgnore; +import java.util.List; +import java.util.stream.Collectors; +import org.mapstruct.Context; +import org.mapstruct.MappingTarget; + +public interface BaseCycleAvoidingMapper extends BaseMapper { + + @DoIgnore + T convert(S source, @Context CycleAvoidingMappingContext context); + + @DoIgnore + T convert(S source, @MappingTarget T target, @Context CycleAvoidingMappingContext context); + + @DoIgnore + default List convert(List sourceList, @Context CycleAvoidingMappingContext context) { + return sourceList.stream() + .map(item -> convert(item, context)) + .collect(Collectors.toList()); + } + + @Override + @DoIgnore + default T convert(S source) { + return convert(source, new CycleAvoidingMappingContext()); + } + + @Override + @DoIgnore + default T convert(S source, @MappingTarget T target) { + return convert(source, new CycleAvoidingMappingContext()); + } + + + @Override + @DoIgnore + default List convert(List sourceList) { + return convert(sourceList, new CycleAvoidingMappingContext()); + } + +} diff --git a/mapstruct-plus/src/main/java/io/github/linpeilie/BaseMapMapper.java b/mapstruct-plus/src/main/java/io/github/linpeilie/BaseMapMapper.java index b40b064c186306dbfbb1f064dc4f5fab064b1a3e..f9ca9597d90508db34b0133931258adb2b89b861 100644 --- a/mapstruct-plus/src/main/java/io/github/linpeilie/BaseMapMapper.java +++ b/mapstruct-plus/src/main/java/io/github/linpeilie/BaseMapMapper.java @@ -6,14 +6,4 @@ public interface BaseMapMapper { T convert(Map map); - default T convertByObj(Object obj) { - if (obj == null) { - return null; - } - if (obj instanceof Map) { - return convert((Map) obj); - } - return null; - } - } 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 e031b5a13163f2a6615c48841abb36d9b6d658c4..a89fa3e36f6082ecc9144e54022a6c0cc9a20c7c 100644 --- a/mapstruct-plus/src/main/java/io/github/linpeilie/BaseMapper.java +++ b/mapstruct-plus/src/main/java/io/github/linpeilie/BaseMapper.java @@ -1,20 +1,23 @@ package io.github.linpeilie; -import cn.hutool.core.collection.CollectionUtil; +import io.github.linpeilie.annotations.DoIgnore; +import io.github.linpeilie.utils.CollectionUtils; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.stream.Collectors; import org.mapstruct.MappingTarget; public interface BaseMapper { + @DoIgnore T convert(S source); + @DoIgnore T convert(S source, @MappingTarget T target); + @DoIgnore default List convert(List sourceList) { - if (CollectionUtil.isEmpty(sourceList)) { + if (CollectionUtils.isEmpty(sourceList)) { 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 459c22e10c65202e7848e72c24711b51437de832..92ae3a717d708e25fde2c0cae008f7f32c8c1344 100644 --- a/mapstruct-plus/src/main/java/io/github/linpeilie/Converter.java +++ b/mapstruct-plus/src/main/java/io/github/linpeilie/Converter.java @@ -48,12 +48,36 @@ public class Converter { } public List convert(List source, Class targetType) { - if (source == null || source.size() == 0) { + if (source == null || source.isEmpty()) { return new ArrayList<>(); } return source.stream().map(item -> convert(item, targetType)).collect(Collectors.toList()); } + + @SuppressWarnings("unchecked") + public T convert(S source, Class target, CycleAvoidingMappingContext context) { + if (source == null) { + return null; + } + BaseCycleAvoidingMapper mapper = + (BaseCycleAvoidingMapper) converterFactory.getCycleAvoidingMapper(source.getClass(), target); + if (mapper != null) { + return mapper.convert(source, context); + } + throw new ConvertException("cannot find converter from " + source.getClass().getSimpleName() + " to " + + target.getSimpleName()); + } + + public List convert(List source, Class targetType, CycleAvoidingMappingContext context) { + if (source == null || source.isEmpty()) { + return new ArrayList<>(); + } + return source.stream().map(item -> convert(item, targetType, context)).collect(Collectors.toList()); + } + + + public T convert(Map map, Class target) { if (map == null || map.isEmpty()) { return null; diff --git a/mapstruct-plus/src/main/java/io/github/linpeilie/ConverterFactory.java b/mapstruct-plus/src/main/java/io/github/linpeilie/ConverterFactory.java index 852559ca319e7ae7c54b36a748c557d6decbfe4b..0242d87368646bed7d2b339d9ea72c6867af6461 100644 --- a/mapstruct-plus/src/main/java/io/github/linpeilie/ConverterFactory.java +++ b/mapstruct-plus/src/main/java/io/github/linpeilie/ConverterFactory.java @@ -4,6 +4,8 @@ public interface ConverterFactory { BaseMapper getMapper(Class sourceType, Class targetType); + BaseCycleAvoidingMapper getCycleAvoidingMapper(Class sourceType, Class targetType); + BaseMapMapper getMapMapper(Class sourceType); } diff --git a/mapstruct-plus/src/main/java/io/github/linpeilie/CycleAvoidingMappingContext.java b/mapstruct-plus/src/main/java/io/github/linpeilie/CycleAvoidingMappingContext.java new file mode 100644 index 0000000000000000000000000000000000000000..0e10832d60dac35832d7f5d63275572c60e5576b --- /dev/null +++ b/mapstruct-plus/src/main/java/io/github/linpeilie/CycleAvoidingMappingContext.java @@ -0,0 +1,36 @@ +package io.github.linpeilie; + +import java.util.HashMap; +import java.util.IdentityHashMap; +import java.util.Map; +import org.mapstruct.BeforeMapping; +import org.mapstruct.MappingTarget; +import org.mapstruct.TargetType; + +public class CycleAvoidingMappingContext { + + private Map, Object>> knownInstances = new IdentityHashMap<>(); + + @BeforeMapping + public T getMappedInstance(Object source, @TargetType Class targetType) { + Map, Object> map = knownInstances.get(source); + if (map == null || map.isEmpty()) { + return null; + } + return (T) map.get(targetType); + } + + @BeforeMapping + public void storeMappedInstance(Object source, @MappingTarget Object target) { + if (target == null) { + return; + } + Map, Object> map = knownInstances.get(source); + if (map == null) { + map = new HashMap<>(); + } + map.put(target.getClass(), target); + knownInstances.put( source, map ); + } + +} diff --git a/mapstruct-plus/src/main/java/io/github/linpeilie/annotations/AutoMapper.java b/mapstruct-plus/src/main/java/io/github/linpeilie/annotations/AutoMapper.java index d216afd65716784c1e1bfd53028f070c9739c558..fc2b90c41c3d6d2a50c52937f8879d1b2d683e90 100644 --- a/mapstruct-plus/src/main/java/io/github/linpeilie/annotations/AutoMapper.java +++ b/mapstruct-plus/src/main/java/io/github/linpeilie/annotations/AutoMapper.java @@ -32,4 +32,14 @@ public @interface AutoMapper { */ boolean reverseConvertGenerate() default true; + /** + * 是否需要避免对象循环嵌套 + *

+ * 循环嵌套:A中有属性类型B,B中有属性类型A,且可能对象之间互相引用 + *

+ * + * @return true: 需要避免对象循环嵌套;false:不需要 + */ + boolean cycleAvoiding() default false; + } diff --git a/mapstruct-plus/src/main/java/io/github/linpeilie/annotations/AutoMapping.java b/mapstruct-plus/src/main/java/io/github/linpeilie/annotations/AutoMapping.java index 16d8e5846f326f008955d9c873bf50f00b5f1f3a..df8217645f0ca350a1edcd8d9cfd0990eaeda647 100644 --- a/mapstruct-plus/src/main/java/io/github/linpeilie/annotations/AutoMapping.java +++ b/mapstruct-plus/src/main/java/io/github/linpeilie/annotations/AutoMapping.java @@ -40,4 +40,10 @@ public @interface AutoMapping { */ String defaultValue() default ""; + String[] qualifiedByName() default {}; + + String[] conditionQualifiedByName() default {}; + + String[] dependsOn() default {}; + } diff --git a/mapstruct-plus/src/main/java/io/github/linpeilie/annotations/AutoMappings.java b/mapstruct-plus/src/main/java/io/github/linpeilie/annotations/AutoMappings.java index b88fc0654ae0bd3453f1ab9e0ffa94b98333fdeb..d8f0cb649c64e7e5336eaa9c5f70d15dc8a3dd12 100644 --- a/mapstruct-plus/src/main/java/io/github/linpeilie/annotations/AutoMappings.java +++ b/mapstruct-plus/src/main/java/io/github/linpeilie/annotations/AutoMappings.java @@ -5,7 +5,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -@Target(ElementType.FIELD) +@Target({ElementType.FIELD, ElementType.METHOD}) @Retention(RetentionPolicy.CLASS) public @interface AutoMappings { diff --git a/mapstruct-plus/src/main/java/io/github/linpeilie/annotations/DoIgnore.java b/mapstruct-plus/src/main/java/io/github/linpeilie/annotations/DoIgnore.java new file mode 100644 index 0000000000000000000000000000000000000000..abd5ecf2cbd0180e1d60b8d7fc6b29203445b1e4 --- /dev/null +++ b/mapstruct-plus/src/main/java/io/github/linpeilie/annotations/DoIgnore.java @@ -0,0 +1,13 @@ +package io.github.linpeilie.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.mapstruct.Qualifier; + +@Qualifier +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.CLASS) +public @interface DoIgnore { +} diff --git a/mapstruct-plus/src/main/java/io/github/linpeilie/annotations/ReverseAutoMapping.java b/mapstruct-plus/src/main/java/io/github/linpeilie/annotations/ReverseAutoMapping.java index ed5c9a459f7837ab7860b9c819c8770432164a8d..17cd2990147481d180557aa342a4d22e9c646b98 100644 --- a/mapstruct-plus/src/main/java/io/github/linpeilie/annotations/ReverseAutoMapping.java +++ b/mapstruct-plus/src/main/java/io/github/linpeilie/annotations/ReverseAutoMapping.java @@ -67,4 +67,10 @@ public @interface ReverseAutoMapping { */ String defaultValue() default ""; + String[] qualifiedByName() default {}; + + String[] conditionQualifiedByName() default {}; + + String[] dependsOn() default {}; + } diff --git a/mapstruct-plus/src/main/java/io/github/linpeilie/utils/ArrayUtil.java b/mapstruct-plus/src/main/java/io/github/linpeilie/utils/ArrayUtil.java new file mode 100644 index 0000000000000000000000000000000000000000..524ed5774fdeefe462999130716b7e3cbd7d695d --- /dev/null +++ b/mapstruct-plus/src/main/java/io/github/linpeilie/utils/ArrayUtil.java @@ -0,0 +1,23 @@ +package io.github.linpeilie.utils; + +import java.util.Arrays; +import java.util.stream.Collectors; + +public class ArrayUtil { + + public static boolean isNotEmpty(T[] arr) { + return arr != null && arr.length > 0; + } + + public static String join(T[] arr, CharSequence delimiter) { + return join(arr, delimiter, "", ""); + } + + public static String join(T[] arr, CharSequence delimiter, String prefix, String suffix) { + if (arr == null) { + return null; + } + return Arrays.stream(arr).map(str -> prefix + str + suffix).collect(Collectors.joining(delimiter)); + } + +} diff --git a/mapstruct-plus/src/main/java/io/github/linpeilie/utils/CharUtils.java b/mapstruct-plus/src/main/java/io/github/linpeilie/utils/CharUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..284600dd62387126742df8fcf4fa8c308711e29f --- /dev/null +++ b/mapstruct-plus/src/main/java/io/github/linpeilie/utils/CharUtils.java @@ -0,0 +1,41 @@ +package io.github.linpeilie.utils; + +public class CharUtils { + /** + * 是否空白符
+ * 空白符包括空格、制表符、全角空格和不间断空格
+ * + * @param c 字符 + * @return 是否空白符 + * @see Character#isWhitespace(int) + * @see Character#isSpaceChar(int) + * @since 4.0.10 + */ + public static boolean isBlankChar(char c) { + return isBlankChar((int) c); + } + + /** + * 是否空白符
+ * 空白符包括空格、制表符、全角空格和不间断空格
+ * + * @param c 字符 + * @return 是否空白符 + * @see Character#isWhitespace(int) + * @see Character#isSpaceChar(int) + * @since 4.0.10 + */ + public static boolean isBlankChar(int c) { + return Character.isWhitespace(c) + || Character.isSpaceChar(c) + || c == '\ufeff' + || c == '\u202a' + || c == '\u0000' + // issue#I5UGSQ,Hangul Filler + || c == '\u3164' + // Braille Pattern Blank + || c == '\u2800' + // MONGOLIAN VOWEL SEPARATOR + || c == '\u180e'; + } +} diff --git a/mapstruct-plus/src/main/java/io/github/linpeilie/utils/CollectionUtils.java b/mapstruct-plus/src/main/java/io/github/linpeilie/utils/CollectionUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..857c0f1381276969f9b9a991f6dac5e21de0eead --- /dev/null +++ b/mapstruct-plus/src/main/java/io/github/linpeilie/utils/CollectionUtils.java @@ -0,0 +1,29 @@ +package io.github.linpeilie.utils; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; + +public class CollectionUtils { + + public static boolean isEmpty(Collection collection) { + return collection == null || collection.isEmpty(); + } + + public static boolean isNotEmpty(Collection collection) { + return !isEmpty(collection); + } + + public static List newArrayList(T... values) { + if (values == null || values.length == 0) { + return new ArrayList<>(); + } + List arrayList = new ArrayList<>(values.length); + Collections.addAll(arrayList, values); + return arrayList; + } + +} diff --git a/mapstruct-plus/src/main/java/io/github/linpeilie/utils/StrUtil.java b/mapstruct-plus/src/main/java/io/github/linpeilie/utils/StrUtil.java new file mode 100644 index 0000000000000000000000000000000000000000..765e62d23688c3526bb90a2cd2cffb82fcd62d47 --- /dev/null +++ b/mapstruct-plus/src/main/java/io/github/linpeilie/utils/StrUtil.java @@ -0,0 +1,86 @@ +package io.github.linpeilie.utils; + +public class StrUtil { + + /** + * 去掉首部指定长度的字符串并将剩余字符串首字母小写
例如:str=setName, preLength=3 =》 return name + * + * @param str 被处理的字符串 + * @param preLength 去掉的长度 + * @return 处理后的字符串,不符合规范返回null + */ + public static String removePreAndLowerFirst(CharSequence str, int preLength) { + if (str == null) { + return null; + } + if (str.length() > preLength) { + char first = Character.toLowerCase(str.charAt(preLength)); + if (str.length() > preLength + 1) { + return first + str.toString().substring(preLength + 1); + } + return String.valueOf(first); + } else { + return str.toString(); + } + } + + /** + * 获得set或get或is方法对应的标准属性名
例如:setName 返回 name + * + *
+     * getName =》name
+     * setName =》name
+     * isName  =》name
+     * 
+ * + * @param getOrSetMethodName Get或Set方法名 + * @return 如果是set或get方法名,返回field, 否则null + */ + public static String getGeneralField(CharSequence getOrSetMethodName) { + final String getOrSetMethodNameStr = getOrSetMethodName.toString(); + if (getOrSetMethodNameStr.startsWith("get") || getOrSetMethodNameStr.startsWith("set")) { + return removePreAndLowerFirst(getOrSetMethodName, 3); + } else if (getOrSetMethodNameStr.startsWith("is")) { + return removePreAndLowerFirst(getOrSetMethodName, 2); + } + return null; + } + + public static boolean isEmpty(CharSequence cs) { + return cs == null || cs.length() == 0; + } + + public static boolean isNotEmpty(CharSequence cs) { + return !isEmpty(cs); + } + + public static boolean equalsIgnoreCase(CharSequence str1, CharSequence str2) { + if (null == str1) { + // 只有两个都为null才判断相等 + return str2 == null; + } + if (null == str2) { + // 字符串2空,字符串1非空,直接false + return false; + } + + return str1.toString().equalsIgnoreCase(str2.toString()); + } + + public static boolean isBlank(CharSequence str) { + final int length; + if ((str == null) || ((length = str.length()) == 0)) { + return true; + } + + for (int i = 0; i < length; i++) { + // 只要有一个非空字符即为非空字符串 + if (false == CharUtils.isBlankChar(str.charAt(i))) { + return false; + } + } + + return true; + } + +} diff --git a/pom.xml b/pom.xml index c908be3a52fd4e3e07a12d9c7b7c9e0af827cddd..904ab4598246053d6b12f4a823b04488155d8936 100644 --- a/pom.xml +++ b/pom.xml @@ -14,20 +14,26 @@ mapstruct-plus mapstruct-plus-spring-boot-starter mapstruct-plus-processor + mapstruct-plus-object-convert - 1.3.6 + 1.4.0-R1 8 8 UTF-8 1.5.5.Final - 5.8.15 + 5.8.26 https://github.com/linpeilie/mapstruct-plus.git + + io.github.linpeilie + mapstruct-plus-object-convert + ${mapstruct-plus.version} + io.github.linpeilie mapstruct-plus @@ -43,11 +49,6 @@ javapoet 1.9.0 - - org.apache.commons - commons-lang3 - 3.12.0 - org.mapstruct mapstruct