增加map转对象的功能

This commit is contained in:
linpeilie 2023-02-25 23:57:52 +08:00
parent a7be26b6e9
commit ebad18447c
39 changed files with 980 additions and 228 deletions

View File

@ -18,7 +18,7 @@
<maven.compiler.target>8</maven.compiler.target> <maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<mapstruct.version>1.5.1.Final</mapstruct.version> <mapstruct.version>1.5.1.Final</mapstruct.version>
<mapstruct-plus.version>1.0.0</mapstruct-plus.version> <mapstruct-plus.version>1.1.0</mapstruct-plus.version>
<spring-boot.version>2.7.0</spring-boot.version> <spring-boot.version>2.7.0</spring-boot.version>
</properties> </properties>
@ -39,11 +39,6 @@
<artifactId>mapstruct-plus</artifactId> <artifactId>mapstruct-plus</artifactId>
<version>${mapstruct-plus.version}</version> <version>${mapstruct-plus.version}</version>
</dependency> </dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>${mapstruct.version}</version>
</dependency>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId> <artifactId>spring-boot-dependencies</artifactId>

View File

@ -22,6 +22,11 @@
<groupId>io.github.linpeilie</groupId> <groupId>io.github.linpeilie</groupId>
<artifactId>mapstruct-plus</artifactId> <artifactId>mapstruct-plus</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
</dependencies> </dependencies>
</project> </project>

View File

@ -0,0 +1,12 @@
package io.github.linpeilie;
import java.util.Arrays;
import java.util.List;
public class StringToListStringConverter {
public static List<String> stringToListString(String str) {
return Arrays.asList(str.split(","));
}
}

View File

@ -0,0 +1,32 @@
package io.github.linpeilie.model;
public class Goods {
private String price;
private String takeDownTime;
public String getPrice() {
return price;
}
public void setPrice(final String price) {
this.price = price;
}
public String getTakeDownTime() {
return takeDownTime;
}
public void setTakeDownTime(final String takeDownTime) {
this.takeDownTime = takeDownTime;
}
@Override
public String toString() {
return "Goods{" +
"price='" + price + '\'' +
", takeDownTime='" + takeDownTime + '\'' +
'}';
}
}

View File

@ -0,0 +1,39 @@
package io.github.linpeilie.model;
import io.github.linpeilie.annotations.AutoMapper;
import io.github.linpeilie.annotations.AutoMapping;
import java.util.Date;
@AutoMapper(target = Goods.class)
public class GoodsDto {
@AutoMapping(target = "takeDownTime", dateFormat = "yyyy-MM-dd HH:mm:ss")
private Date takeDownTime;
@AutoMapping(target = "price", numberFormat = "$#.00")
private int price;
public int getPrice() {
return price;
}
public void setPrice(final int price) {
this.price = price;
}
public Date getTakeDownTime() {
return takeDownTime;
}
public void setTakeDownTime(final Date takeDownTime) {
this.takeDownTime = takeDownTime;
}
@Override
public String toString() {
return "GoodsDto{" +
"takeDownTime=" + takeDownTime +
", price=" + price +
'}';
}
}

View File

@ -0,0 +1,54 @@
package io.github.linpeilie.model;
import io.github.linpeilie.annotations.AutoMapMapper;
@AutoMapMapper
public class MapModelA {
private String str;
private int i1;
private Long l2;
private MapModelB mapModelB;
public String getStr() {
return str;
}
public void setStr(final String str) {
this.str = str;
}
public int getI1() {
return i1;
}
public void setI1(final int i1) {
this.i1 = i1;
}
public Long getL2() {
return l2;
}
public void setL2(final Long l2) {
this.l2 = l2;
}
public MapModelB getMapModelB() {
return mapModelB;
}
@Override
public String toString() {
return "MapModelA{" +
"str='" + str + '\'' +
", i1=" + i1 +
", l2=" + l2 +
", mapModelB=" + mapModelB +
'}';
}
public void setMapModelB(final MapModelB mapModelB) {
this.mapModelB = mapModelB;
}
}

View File

@ -0,0 +1,25 @@
package io.github.linpeilie.model;
import io.github.linpeilie.annotations.AutoMapMapper;
import java.util.Date;
@AutoMapMapper
public class MapModelB {
private Date date;
public Date getDate() {
return date;
}
public void setDate(final Date date) {
this.date = date;
}
@Override
public String toString() {
return "MapModelB{" +
"date=" + date +
'}';
}
}

View File

@ -1,15 +1,27 @@
package io.github.linpeilie.model; package io.github.linpeilie.model;
import io.github.linpeilie.annotations.AutoMapper; import io.github.linpeilie.annotations.AutoMapper;
import java.util.Objects; import io.github.linpeilie.annotations.AutoMapping;
import java.util.Date;
import java.util.List;
@AutoMapper(target = UserDto.class) @AutoMapper(target = UserDto.class)
public class User { public class User {
private String username; private String username;
private int age; private int age;
private boolean young; private boolean young;
@AutoMapping(target = "educations", expression = "java(java.lang.String.join(\",\", source.getEducationList()))")
private List<String> educationList;
@AutoMapping(target = "birthday", dateFormat = "yyyy-MM-dd HH:mm:ss")
private Date birthday;
@AutoMapping(target = "assets", numberFormat = "$0.00")
private double assets;
public String getUsername() { public String getUsername() {
return username; return username;
} }
@ -34,22 +46,28 @@ public class User {
this.young = young; this.young = young;
} }
@Override public List<String> getEducationList() {
public boolean equals(final Object o) { return educationList;
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
final User user = (User) o;
return getAge() == user.getAge() && isYoung() == user.isYoung() &&
Objects.equals(getUsername(), user.getUsername());
} }
@Override public void setEducationList(final List<String> educationList) {
public int hashCode() { this.educationList = educationList;
return Objects.hash(getUsername(), getAge(), isYoung()); }
public Date getBirthday() {
return birthday;
}
public void setBirthday(final Date birthday) {
this.birthday = birthday;
}
public double getAssets() {
return assets;
}
public void setAssets(final double assets) {
this.assets = assets;
} }
@Override @Override
@ -58,6 +76,9 @@ public class User {
"username='" + username + '\'' + "username='" + username + '\'' +
", age=" + age + ", age=" + age +
", young=" + young + ", young=" + young +
", educationList=" + educationList +
", birthday=" + birthday +
", assets=" + assets +
'}'; '}';
} }
} }

View File

@ -1,13 +1,23 @@
package io.github.linpeilie.model; package io.github.linpeilie.model;
import java.util.Objects; import io.github.linpeilie.StringToListStringConverter;
import io.github.linpeilie.annotations.AutoMapper;
import io.github.linpeilie.annotations.AutoMapping;
@AutoMapper(target = User.class, uses = StringToListStringConverter.class)
public class UserDto { public class UserDto {
private String username; private String username;
private int age; private int age;
private boolean young; private boolean young;
@AutoMapping(target = "educationList")
private String educations;
private String birthday;
private String assets;
public String getUsername() { public String getUsername() {
return username; return username;
} }
@ -32,22 +42,28 @@ public class UserDto {
this.young = young; this.young = young;
} }
@Override public String getEducations() {
public boolean equals(final Object o) { return educations;
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
final UserDto user = (UserDto) o;
return getAge() == user.getAge() && isYoung() == user.isYoung() &&
Objects.equals(getUsername(), user.getUsername());
} }
@Override public void setEducations(final String educations) {
public int hashCode() { this.educations = educations;
return Objects.hash(getUsername(), getAge(), isYoung()); }
public String getBirthday() {
return birthday;
}
public void setBirthday(final String birthday) {
this.birthday = birthday;
}
public String getAssets() {
return assets;
}
public void setAssets(final String assets) {
this.assets = assets;
} }
@Override @Override
@ -56,7 +72,9 @@ public class UserDto {
"username='" + username + '\'' + "username='" + username + '\'' +
", age=" + age + ", age=" + age +
", young=" + young + ", young=" + young +
", educations='" + educations + '\'' +
", birthday='" + birthday + '\'' +
", assets='" + assets + '\'' +
'}'; '}';
} }
} }

View File

@ -0,0 +1,94 @@
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;
import io.github.linpeilie.model.User;
import io.github.linpeilie.model.UserDto;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.junit.jupiter.api.Test;
public class QuickStartTest {
private Converter converter = new Converter();
@Test
public void test() {
Map<String, Object> mapModel1 = new HashMap<>();
mapModel1.put("str", "1jkf1ijkj3f");
mapModel1.put("i1", 111);
mapModel1.put("l2", 11231);
Map<String, Object> mapModel2 = new HashMap<>();
mapModel2.put("date", DateUtil.parse("2023-02-23 01:03:23"));
mapModel1.put("mapModelB", mapModel2);
final MapModelA mapModelA = converter.convert(mapModel1, MapModelA.class);
System.out.println(mapModelA);
}
@Test
public void ueseTest() {
UserDto userDto = new UserDto();
userDto.setEducations("1,2,3");
final User user = converter.convert(userDto, User.class);
System.out.println(user.getEducationList()); // [1, 2, 3]
assert user.getEducationList().size() == 3;
}
@Test
public void numberFormatTest() {
GoodsDto goodsDto = new GoodsDto();
goodsDto.setPrice(9);
final Goods goods = converter.convert(goodsDto, Goods.class);
System.out.println(goods.getPrice()); // $9.00
assert "$9.00".equals(goods.getPrice());
}
@Test
public void dateFormatTest() throws ParseException {
final GoodsDto goodsDto = new GoodsDto();
final SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
final String dateString = "2023-02-23 23:27:37";
final Date date = simpleDateFormat.parse(dateString);
goodsDto.setTakeDownTime(date);
final Goods goods = converter.convert(goodsDto, Goods.class);
System.out.println(goods.getTakeDownTime()); // 2023-02-23 23:27:37
assert dateString.equals(goods.getTakeDownTime());
}
@Test
public void expressionFormatTest() {
User user = new User();
List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
list.add("3");
user.setEducationList(list);
final UserDto userDto = converter.convert(user, UserDto.class);
System.out.println(userDto.getEducations());
assert "1,2,3".equals(userDto.getEducations());
}
}

View File

@ -0,0 +1,54 @@
package io.github.linpeilie.model;
import io.github.linpeilie.annotations.AutoMapMapper;
@AutoMapMapper
public class MapModelA {
private String str;
private int i1;
private Long l2;
private MapModelB mapModelB;
public String getStr() {
return str;
}
public void setStr(final String str) {
this.str = str;
}
public int getI1() {
return i1;
}
public void setI1(final int i1) {
this.i1 = i1;
}
public Long getL2() {
return l2;
}
public void setL2(final Long l2) {
this.l2 = l2;
}
public MapModelB getMapModelB() {
return mapModelB;
}
@Override
public String toString() {
return "MapModelA{" +
"str='" + str + '\'' +
", i1=" + i1 +
", l2=" + l2 +
", mapModelB=" + mapModelB +
'}';
}
public void setMapModelB(final MapModelB mapModelB) {
this.mapModelB = mapModelB;
}
}

View File

@ -0,0 +1,25 @@
package io.github.linpeilie.model;
import io.github.linpeilie.annotations.AutoMapMapper;
import java.util.Date;
@AutoMapMapper
public class MapModelB {
private Date date;
public Date getDate() {
return date;
}
public void setDate(final Date date) {
this.date = date;
}
@Override
public String toString() {
return "MapModelB{" +
"date=" + date +
'}';
}
}

View File

@ -1,14 +1,19 @@
package io.github.linpeilie; package io.github.linpeilie;
import cn.hutool.core.date.DateUtil;
import io.github.linpeilie.model.Goods; import io.github.linpeilie.model.Goods;
import io.github.linpeilie.model.GoodsDto; import io.github.linpeilie.model.GoodsDto;
import io.github.linpeilie.model.MapModelA;
import io.github.linpeilie.model.User; import io.github.linpeilie.model.User;
import io.github.linpeilie.model.UserDto; import io.github.linpeilie.model.UserDto;
import java.text.ParseException; import java.text.ParseException;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date; import java.util.Date;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest;
@ -22,27 +27,21 @@ public class QuickStartTest {
@Test @Test
public void test() { public void test() {
User user = new User(); Map<String, Object> mapModel1 = new HashMap<>();
user.setUsername("jack"); mapModel1.put("str", "1jkf1ijkj3f");
user.setAge(23); mapModel1.put("i1", 111);
user.setYoung(false); mapModel1.put("l2", 11231);
UserDto userDto = converter.convert(user, UserDto.class); Map<String, Object> mapModel2 = new HashMap<>();
System.out.println(userDto); // UserDto{username='jack', age=23, young=false} mapModel2.put("date", DateUtil.parse("2023-02-23 01:03:23"));
assert user.getUsername().equals(userDto.getUsername()); mapModel1.put("mapModelB", mapModel2);
assert user.getAge() == userDto.getAge();
assert user.isYoung() == userDto.isYoung();
User newUser = converter.convert(userDto, User.class); final MapModelA mapModelA = converter.convert(mapModel1, MapModelA.class);
System.out.println(mapModelA);
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();
} }
@Test @Test
public void ueseTest() { public void ueseTest() {
UserDto userDto = new UserDto(); UserDto userDto = new UserDto();

View File

@ -2,6 +2,7 @@ package io.github.linpeilie.processor;
import com.squareup.javapoet.JavaFile; import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.TypeSpec; import com.squareup.javapoet.TypeSpec;
import io.github.linpeilie.processor.metadata.AbstractAdapterMethodMetadata;
import java.io.IOException; import java.io.IOException;
import java.io.Writer; import java.io.Writer;
import java.util.Collection; import java.util.Collection;
@ -11,26 +12,22 @@ import static javax.tools.Diagnostic.Kind.ERROR;
public abstract class AbstractAdapterMapperGenerator { public abstract class AbstractAdapterMapperGenerator {
public void write(ProcessingEnvironment processingEnv, Collection<AdapterMethodMetadata> adapterMethods) { public void write(ProcessingEnvironment processingEnv, Collection<AbstractAdapterMethodMetadata> adapterMethods, String adapterClassName) {
// write Adapter // write Adapter
try (final Writer writer = processingEnv.getFiler() try (final Writer writer = processingEnv.getFiler()
.createSourceFile(adapterPackage() + "." + adapterClassName()) .createSourceFile(adapterPackage() + "." + adapterClassName)
.openWriter()) { .openWriter()) {
JavaFile.builder(adapterPackage(), createTypeSpec(adapterMethods)).build().writeTo(writer); JavaFile.builder(adapterPackage(), createTypeSpec(adapterMethods, adapterClassName)).build().writeTo(writer);
} catch (IOException e) { } catch (IOException e) {
processingEnv.getMessager() processingEnv.getMessager()
.printMessage(ERROR, "Error while opening " + adapterClassName() + " output file: " + e.getMessage()); .printMessage(ERROR, "Error while opening " + adapterClassName + " output file: " + e.getMessage());
} }
} }
protected abstract TypeSpec createTypeSpec(Collection<AdapterMethodMetadata> adapterMethods); protected abstract TypeSpec createTypeSpec(Collection<AbstractAdapterMethodMetadata> adapterMethods, String adapterClassName);
protected String adapterPackage() { protected String adapterPackage() {
return AutoMapperProperties.getAdapterPackage(); return AutoMapperProperties.getAdapterPackage();
} }
protected String adapterClassName() {
return AutoMapperProperties.getAdapterClassName();
}
} }

View File

@ -1,14 +1,25 @@
package io.github.linpeilie.processor; package io.github.linpeilie.processor;
import com.squareup.javapoet.ClassName; import com.squareup.javapoet.ClassName;
import io.github.linpeilie.annotations.AutoMapMapper;
import io.github.linpeilie.annotations.AutoMapper; import io.github.linpeilie.annotations.AutoMapper;
import io.github.linpeilie.annotations.AutoMapping; import io.github.linpeilie.annotations.AutoMapping;
import io.github.linpeilie.annotations.ComponentModelConfig; import io.github.linpeilie.annotations.ComponentModelConfig;
import io.github.linpeilie.annotations.MapperConfig; import io.github.linpeilie.annotations.MapperConfig;
import java.awt.Component; import io.github.linpeilie.processor.generator.AutoMapperGenerator;
import io.github.linpeilie.processor.generator.DefaultAdapterMapperGenerator;
import io.github.linpeilie.processor.generator.MapperConfigGenerator;
import io.github.linpeilie.processor.generator.SpringAdapterMapperGenerator;
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.metadata.AutoMapMapperMetadata;
import io.github.linpeilie.processor.metadata.AutoMapperMetadata;
import io.github.linpeilie.processor.metadata.AutoMappingMetadata;
import java.io.IOException; import java.io.IOException;
import java.io.Writer; import java.io.Writer;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
@ -31,11 +42,13 @@ import org.apache.commons.lang3.StringUtils;
import org.mapstruct.MappingConstants; import org.mapstruct.MappingConstants;
import static io.github.linpeilie.processor.Constants.AUTO_MAPPER_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.COMPONENT_MODEL_CONFIG_ANNOTATION;
import static io.github.linpeilie.processor.Constants.MAPPER_CONFIG_ANNOTATION; import static io.github.linpeilie.processor.Constants.MAPPER_CONFIG_ANNOTATION;
import static javax.tools.Diagnostic.Kind.ERROR; import static javax.tools.Diagnostic.Kind.ERROR;
@SupportedAnnotationTypes({AUTO_MAPPER_ANNOTATION, MAPPER_CONFIG_ANNOTATION, COMPONENT_MODEL_CONFIG_ANNOTATION}) @SupportedAnnotationTypes({AUTO_MAPPER_ANNOTATION, AUTO_MAP_MAPPER_ANNOTATION, MAPPER_CONFIG_ANNOTATION,
COMPONENT_MODEL_CONFIG_ANNOTATION})
public class AutoMapperProcessor extends AbstractProcessor { public class AutoMapperProcessor extends AbstractProcessor {
private final AutoMapperGenerator mapperGenerator; private final AutoMapperGenerator mapperGenerator;
@ -44,7 +57,9 @@ public class AutoMapperProcessor extends AbstractProcessor {
private final MapperConfigGenerator mapperConfigGenerator; private final MapperConfigGenerator mapperConfigGenerator;
private final Map<String, AdapterMethodMetadata> methodMap = new HashMap<>(); private final Map<String, AbstractAdapterMethodMetadata> methodMap = new HashMap<>();
private final Map<String, AbstractAdapterMethodMetadata> mapMethodMap = new HashMap<>();
private final Set<String> mapperSet = new HashSet<>(); private final Set<String> mapperSet = new HashSet<>();
@ -57,6 +72,10 @@ public class AutoMapperProcessor extends AbstractProcessor {
return AUTO_MAPPER_ANNOTATION.contentEquals(annotation.getQualifiedName()); return AUTO_MAPPER_ANNOTATION.contentEquals(annotation.getQualifiedName());
} }
private boolean isAutoMapMapperAnnotation(TypeElement annotation) {
return AUTO_MAP_MAPPER_ANNOTATION.contentEquals(annotation.getQualifiedName());
}
private boolean isMapperConfigAnnotation(TypeElement annotation) { private boolean isMapperConfigAnnotation(TypeElement annotation) {
return MAPPER_CONFIG_ANNOTATION.contentEquals(annotation.getQualifiedName()); return MAPPER_CONFIG_ANNOTATION.contentEquals(annotation.getQualifiedName());
} }
@ -71,22 +90,62 @@ public class AutoMapperProcessor extends AbstractProcessor {
if (!hasAutoMapper) { if (!hasAutoMapper) {
return false; return false;
} }
processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "start refresh properties"); // 刷新配置
refreshProperties(annotations, roundEnv); refreshProperties(annotations, roundEnv);
// 根据配置生成注解类
this.adapterMapperGenerator = AutoMapperProperties.getComponentModel() this.adapterMapperGenerator = AutoMapperProperties.getComponentModel()
.contentEquals( .contentEquals(
MappingConstants.ComponentModel.SPRING) ? new SpringAdapterMapperGenerator() : new DefaultAdapterMapperGenerator(); MappingConstants.ComponentModel.SPRING) ? new SpringAdapterMapperGenerator() : new DefaultAdapterMapperGenerator();
processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "start write config class"); // 获取 MapMapper
writeConfigClass(); annotations.stream()
processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "start generate mapper class"); .filter(this::isAutoMapMapperAnnotation)
.findFirst()
.ifPresent(annotation -> processAutoMapMapperAnnotation(roundEnv, annotation));
// 生成类
annotations.stream() annotations.stream()
.filter(this::isAutoMapperAnnotation) .filter(this::isAutoMapperAnnotation)
.forEach(annotation -> processAutoMapperAnnotation(roundEnv, annotation)); .findFirst()
.ifPresent(annotation -> processAutoMapperAnnotation(roundEnv, annotation));
return false; return false;
} }
private void processAutoMapMapperAnnotation(final RoundEnvironment roundEnv, final TypeElement annotation) {
final List<AutoMapperMetadata> autoMapMapperMetadataList =
roundEnv.getElementsAnnotatedWith(annotation).stream().map(ele -> {
if (ele.getAnnotation(AutoMapMapper.class) == null) {
return null;
}
ClassName source = ClassName.get("java.util", "Map");
ClassName target = ClassName.get((TypeElement) ele);
List<ClassName> uses = Arrays.asList(ClassName.get("io.github.linpeilie.map", "MapObjectConvert"));
final AutoMapperMetadata autoMapperMetadata = new AutoMapMapperMetadata();
autoMapperMetadata.setTargetClassName(target);
autoMapperMetadata.setSourceClassName(source);
autoMapperMetadata.setUsesClassNameList(uses);
autoMapperMetadata.setSuperClass(ClassName.get("io.github.linpeilie", "BaseMapMapper"));
autoMapperMetadata.setSuperGenerics(new ClassName[] {target});
autoMapperMetadata.setMapstructConfigClass(ClassName.get(AutoMapperProperties.getConfigPackage(),
AutoMapperProperties.getMapConfigClassName()));
return autoMapperMetadata;
}).filter(Objects::nonNull).collect(Collectors.toList());
autoMapMapperMetadataList.forEach(metadata -> {
this.writeAutoMapperClassFile(metadata);
addAdapterMapMethod(metadata.getSourceClassName(), metadata.getTargetClassName(), metadata.mapperClass(),
false);
addAdapterMapMethod(ClassName.get("java.lang", "Object"), metadata.getTargetClassName(),
metadata.mapperClass(), true);
});
adapterMapperGenerator.write(processingEnv, mapMethodMap.values(),
AutoMapperProperties.getMapAdapterClassName());
mapperConfigGenerator.write(processingEnv, AutoMapperProperties.getMapConfigClassName(),
AutoMapperProperties.getMapAdapterClassName());
}
private void refreshProperties(final Set<? extends TypeElement> annotations, final RoundEnvironment roundEnv) { private void refreshProperties(final Set<? extends TypeElement> annotations, final RoundEnvironment roundEnv) {
annotations.stream() annotations.stream()
.filter(this::isMapperConfigAnnotation) .filter(this::isMapperConfigAnnotation)
@ -116,10 +175,6 @@ public class AutoMapperProcessor extends AbstractProcessor {
return String.valueOf(processingEnv.getElementUtils().getPackageOf(element).getQualifiedName()); return String.valueOf(processingEnv.getElementUtils().getPackageOf(element).getQualifiedName());
} }
private void writeConfigClass() {
mapperConfigGenerator.write(processingEnv);
}
private void processAutoMapperAnnotation(final RoundEnvironment roundEnv, final TypeElement annotation) { private void processAutoMapperAnnotation(final RoundEnvironment roundEnv, final TypeElement annotation) {
final List<AutoMapperMetadata> autoMapperMetadataList = roundEnv.getElementsAnnotatedWith(annotation) final List<AutoMapperMetadata> autoMapperMetadataList = roundEnv.getElementsAnnotatedWith(annotation)
.stream() .stream()
@ -141,15 +196,26 @@ public class AutoMapperProcessor extends AbstractProcessor {
autoMapperMetadataList.addAll(reverseMapperMetadataList); autoMapperMetadataList.addAll(reverseMapperMetadataList);
autoMapperMetadataList.forEach(this::writeAutoMapperClassFile); autoMapperMetadataList.forEach(metadata -> {
this.writeAutoMapperClassFile(metadata);
addAdapterMethod(metadata.getSourceClassName(), metadata.getTargetClassName(), metadata.mapperClass());
});
adapterMapperGenerator.write(processingEnv, methodMap.values()); adapterMapperGenerator.write(processingEnv, methodMap.values(), AutoMapperProperties.getAdapterClassName());
mapperConfigGenerator.write(processingEnv, AutoMapperProperties.getConfigClassName(),
AutoMapperProperties.getAdapterClassName());
} }
private AutoMapperMetadata reverseMapper(AutoMapperMetadata autoMapperMetadata) { private AutoMapperMetadata reverseMapper(AutoMapperMetadata autoMapperMetadata) {
AutoMapperMetadata reverseMapperMetadata = new AutoMapperMetadata(); AutoMapperMetadata reverseMapperMetadata = new AutoMapperMetadata();
reverseMapperMetadata.setSourceClassName(autoMapperMetadata.getTargetClassName()); reverseMapperMetadata.setSourceClassName(autoMapperMetadata.getTargetClassName());
reverseMapperMetadata.setTargetClassName(autoMapperMetadata.getSourceClassName()); reverseMapperMetadata.setTargetClassName(autoMapperMetadata.getSourceClassName());
reverseMapperMetadata.setSuperClass(autoMapperMetadata.getSuperClass());
reverseMapperMetadata.setSuperGenerics(
new ClassName[] {reverseMapperMetadata.getSourceClassName(), reverseMapperMetadata.getTargetClassName()});
reverseMapperMetadata.setMapstructConfigClass(
ClassName.get(AutoMapperProperties.getConfigPackage(), AutoMapperProperties.getConfigClassName()));
// 需要继承的属性 // 需要继承的属性
final List<AutoMappingMetadata> fieldMetadataList = final List<AutoMappingMetadata> fieldMetadataList =
autoMapperMetadata.getFieldMappingList().stream().map(fieldMapping -> { autoMapperMetadata.getFieldMappingList().stream().map(fieldMapping -> {
@ -169,8 +235,6 @@ public class AutoMapperProcessor extends AbstractProcessor {
.createSourceFile(mapperPackage + "." + mapperClassName) .createSourceFile(mapperPackage + "." + mapperClassName)
.openWriter()) { .openWriter()) {
mapperGenerator.write(metadata, writer); mapperGenerator.write(metadata, writer);
addAdapterMethod(metadata.getSourceClassName(), metadata.getTargetClassName(),
ClassName.get(mapperPackage, mapperClassName));
} catch (IOException e) { } catch (IOException e) {
processingEnv.getMessager() processingEnv.getMessager()
.printMessage(ERROR, .printMessage(ERROR,
@ -183,6 +247,12 @@ public class AutoMapperProcessor extends AbstractProcessor {
methodMap.put(adapterMethodMetadata.getMethodName(), adapterMethodMetadata); methodMap.put(adapterMethodMetadata.getMethodName(), adapterMethodMetadata);
} }
private void addAdapterMapMethod(ClassName source, ClassName target, ClassName mapper, boolean objectConverter) {
final AdapterMapMethodMetadata adapterMapMethodMetadata =
new AdapterMapMethodMetadata(source, target, mapper, objectConverter);
mapMethodMap.put(adapterMapMethodMetadata.getMethodName(), adapterMapMethodMetadata);
}
private AutoMapperMetadata buildAutoMapperMetadata(final Element ele) { private AutoMapperMetadata buildAutoMapperMetadata(final Element ele) {
AutoMapper autoMapperAnnotation = ele.getAnnotation(AutoMapper.class); AutoMapper autoMapperAnnotation = ele.getAnnotation(AutoMapper.class);
if (autoMapperAnnotation == null) { if (autoMapperAnnotation == null) {
@ -203,6 +273,10 @@ public class AutoMapperProcessor extends AbstractProcessor {
metadata.setTargetClassName(target); metadata.setTargetClassName(target);
metadata.setUsesClassNameList(uses); metadata.setUsesClassNameList(uses);
metadata.setFieldMappingList(autoMappingMetadataList); metadata.setFieldMappingList(autoMappingMetadataList);
metadata.setSuperClass(ClassName.get("io.github.linpeilie", "BaseMapper"));
metadata.setSuperGenerics(new ClassName[] {source, target});
metadata.setMapstructConfigClass(
ClassName.get(AutoMapperProperties.getConfigPackage(), AutoMapperProperties.getConfigClassName()));
return metadata; return metadata;
} }

View File

@ -20,6 +20,10 @@ public class AutoMapperProperties {
return DEFAULT_ADAPTER_CLASS_NAME; return DEFAULT_ADAPTER_CLASS_NAME;
} }
public static String getMapAdapterClassName() {
return DEFAULT_MAP_ADAPTER_CLASS_NAME;
}
public static String getConfigPackage() { public static String getConfigPackage() {
return DEFAULT_BASE_PACKAGE; return DEFAULT_BASE_PACKAGE;
} }
@ -28,6 +32,10 @@ public class AutoMapperProperties {
return AUTO_MAPPER_CONFIG_CLASS_NAME; return AUTO_MAPPER_CONFIG_CLASS_NAME;
} }
public static String getMapConfigClassName() {
return AUTO_MAP_MAPPER_CONFIG_CLASS_NAME;
}
public static String getComponentModel() { public static String getComponentModel() {
return componentModel; return componentModel;
} }

View File

@ -10,11 +10,16 @@ public class Constants {
public static final String DEFAULT_ADAPTER_CLASS_NAME = "ConvertMapperAdapter"; 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_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_MAPPER_ANNOTATION = "io.github.linpeilie.annotations.AutoMapper";
public static final String AUTO_MAP_MAPPER_ANNOTATION = "io.github.linpeilie.annotations.AutoMapMapper";
public static final String MAPPER_CONFIG_ANNOTATION = "io.github.linpeilie.annotations.MapperConfig"; public static final String MAPPER_CONFIG_ANNOTATION = "io.github.linpeilie.annotations.MapperConfig";
public static final String COMPONENT_MODEL_CONFIG_ANNOTATION = "io.github.linpeilie.annotations.ComponentModelConfig"; public static final String COMPONENT_MODEL_CONFIG_ANNOTATION = "io.github.linpeilie.annotations.ComponentModelConfig";
@ -23,6 +28,8 @@ public class Constants {
public static final String BASE_MAPPER_CLASS_NAME = "BaseMapper"; public static final String BASE_MAPPER_CLASS_NAME = "BaseMapper";
public static final String BASE_MAP_MAPPER_CLASS_NAME = "BaseMapMapper";
public static final String MAPSTRUCT_MAPPER_PACKAGE = "org.mapstruct"; public static final String MAPSTRUCT_MAPPER_PACKAGE = "org.mapstruct";
public static final String MAPSTRUCT_MAPPER_CLASS_NAME = "Mapper"; public static final String MAPSTRUCT_MAPPER_CLASS_NAME = "Mapper";

View File

@ -1,85 +0,0 @@
package io.github.linpeilie.processor;
import com.squareup.javapoet.AnnotationSpec;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.CodeBlock;
import com.squareup.javapoet.FieldSpec;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterSpec;
import com.squareup.javapoet.TypeSpec;
import java.io.IOException;
import java.io.Writer;
import java.util.Collection;
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.Modifier;
import org.mapstruct.MappingConstants;
import static javax.tools.Diagnostic.Kind.ERROR;
public class DefaultAdapterMapperGenerator extends AbstractAdapterMapperGenerator {
public TypeSpec createTypeSpec(Collection<AdapterMethodMetadata> adapterMethods) {
TypeSpec.Builder adapterBuilder = TypeSpec.classBuilder(
ClassName.get(AutoMapperProperties.getAdapterPackage(), AutoMapperProperties.getAdapterClassName()))
.addModifiers(Modifier.PUBLIC);
if (AutoMapperProperties.getComponentModel().contentEquals(MappingConstants.ComponentModel.SPRING)) {
adapterMethods.forEach(adapterMethod -> adapterBuilder.addField(buildMapperField(adapterMethod.getMapper()))
.addMethod(buildMapperSetterMethod(adapterMethod.getMapper()))
.addMethod(buildSpringProxyMethod(adapterMethod)));
} else {
adapterMethods.forEach(adapterMethod -> adapterBuilder.addMethod(buildDefaultProxyMethod(adapterMethod)));
}
return adapterBuilder.build();
}
private MethodSpec buildSpringProxyMethod(final AdapterMethodMetadata adapterMethodMetadata) {
ParameterSpec parameterSpec = ParameterSpec.builder(adapterMethodMetadata.getSource(),
firstWordToLower(adapterMethodMetadata.getSource().simpleName())).build();
return MethodSpec.methodBuilder(firstWordToLower(adapterMethodMetadata.getSource().simpleName()) + "To" +
adapterMethodMetadata.getTarget().simpleName())
.addModifiers(Modifier.PUBLIC)
.addParameter(parameterSpec)
.returns(adapterMethodMetadata.getTarget())
.addStatement("return $N.convert($N)", firstWordToLower(adapterMethodMetadata.getMapper().simpleName()))
.build();
}
private FieldSpec buildMapperField(ClassName mapper) {
return FieldSpec.builder(mapper, firstWordToLower(mapper.simpleName()), Modifier.PRIVATE).build();
}
private String firstWordToLower(String str) {
return str.substring(0, 1).toLowerCase() + str.substring(1);
}
private MethodSpec buildDefaultProxyMethod(AdapterMethodMetadata adapterMethodMetadata) {
ParameterSpec parameterSpec = ParameterSpec.builder(adapterMethodMetadata.getSource(),
firstWordToLower(adapterMethodMetadata.getSource().simpleName())).build();
return MethodSpec.methodBuilder(firstWordToLower(adapterMethodMetadata.getSource().simpleName()) + "To" +
adapterMethodMetadata.getTarget().simpleName())
.addModifiers(Modifier.PUBLIC)
.addParameter(parameterSpec)
.returns(adapterMethodMetadata.getTarget())
.addStatement("return ($T.getMapper($T.class)).convert($N)",
ClassName.get("org.mapstruct.factory", "Mappers"), adapterMethodMetadata.getMapper(),
firstWordToLower(adapterMethodMetadata.getSource().simpleName()))
.build();
}
private MethodSpec buildMapperSetterMethod(ClassName mapper) {
ParameterSpec parameterSpec = buildMapperSetterParameter(mapper);
return MethodSpec.methodBuilder("set" + mapper.simpleName())
.addModifiers(Modifier.PUBLIC)
.addParameter(parameterSpec)
.addStatement("this.$N = $N", buildMapperField(mapper), parameterSpec)
.build();
}
private ParameterSpec buildMapperSetterParameter(ClassName mapper) {
return ParameterSpec.builder(mapper, firstWordToLower(mapper.simpleName())).build();
}
}

View File

@ -1,4 +1,4 @@
package io.github.linpeilie.processor; package io.github.linpeilie.processor.generator;
import com.squareup.javapoet.AnnotationSpec; import com.squareup.javapoet.AnnotationSpec;
import com.squareup.javapoet.ClassName; import com.squareup.javapoet.ClassName;
@ -8,6 +8,9 @@ import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterSpec; import com.squareup.javapoet.ParameterSpec;
import com.squareup.javapoet.ParameterizedTypeName; import com.squareup.javapoet.ParameterizedTypeName;
import com.squareup.javapoet.TypeSpec; import com.squareup.javapoet.TypeSpec;
import io.github.linpeilie.processor.AutoMapperProperties;
import io.github.linpeilie.processor.metadata.AutoMapperMetadata;
import io.github.linpeilie.processor.metadata.AutoMappingMetadata;
import java.io.IOException; import java.io.IOException;
import java.io.UncheckedIOException; import java.io.UncheckedIOException;
import java.io.Writer; import java.io.Writer;
@ -36,8 +39,7 @@ public class AutoMapperGenerator {
private TypeSpec createTypeSpec(AutoMapperMetadata metadata) { private TypeSpec createTypeSpec(AutoMapperMetadata metadata) {
ParameterizedTypeName converterName = ParameterizedTypeName converterName =
ParameterizedTypeName.get(ClassName.get(BASE_MAPPER_PACKAGE, BASE_MAPPER_CLASS_NAME), ParameterizedTypeName.get(metadata.getSuperClass(), metadata.getSuperGenerics());
metadata.getSourceClassName(), metadata.getTargetClassName());
TypeSpec.Builder builder = TypeSpec.interfaceBuilder(metadata.mapperName()) TypeSpec.Builder builder = TypeSpec.interfaceBuilder(metadata.mapperName())
.addSuperinterface(converterName) .addSuperinterface(converterName)
@ -91,8 +93,7 @@ public class AutoMapperGenerator {
// config // config
CodeBlock configCodeBlock = CodeBlock.builder() CodeBlock configCodeBlock = CodeBlock.builder()
.add("$T.class", .add("$T.class", metadata.getMapstructConfigClass())
ClassName.get(AutoMapperProperties.getConfigPackage(), AutoMapperProperties.getConfigClassName()))
.build(); .build();
// uses // uses

View File

@ -0,0 +1,42 @@
package io.github.linpeilie.processor.generator;
import com.squareup.javapoet.ClassName;
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 javax.lang.model.element.Modifier;
public class DefaultAdapterMapperGenerator extends AbstractAdapterMapperGenerator {
public TypeSpec createTypeSpec(Collection<AbstractAdapterMethodMetadata> adapterMethods, String adapterClassName) {
TypeSpec.Builder adapterBuilder = TypeSpec.classBuilder(
ClassName.get(adapterPackage(), adapterClassName))
.addModifiers(Modifier.PUBLIC);
adapterMethods.forEach(adapterMethod -> adapterBuilder.addMethod(buildDefaultProxyMethod(adapterMethod)));
return adapterBuilder.build();
}
private String firstWordToLower(String str) {
return str.substring(0, 1).toLowerCase() + str.substring(1);
}
private MethodSpec buildDefaultProxyMethod(AbstractAdapterMethodMetadata adapterMethodMetadata) {
ParameterSpec parameterSpec = ParameterSpec.builder(adapterMethodMetadata.getSource(),
firstWordToLower(adapterMethodMetadata.getSource().simpleName())).build();
return MethodSpec.methodBuilder(adapterMethodMetadata.getMethodName())
.addModifiers(Modifier.PUBLIC)
.addParameter(parameterSpec)
.returns(adapterMethodMetadata.getReturn())
.addStatement("return ($T.getMapper($T.class)).$N($N)",
ClassName.get("org.mapstruct.factory", "Mappers"), adapterMethodMetadata.getMapper(),
adapterMethodMetadata.getMapperMethodName(),
firstWordToLower(adapterMethodMetadata.getSource().simpleName()))
.build();
}
}

View File

@ -1,10 +1,11 @@
package io.github.linpeilie.processor; package io.github.linpeilie.processor.generator;
import com.squareup.javapoet.AnnotationSpec; import com.squareup.javapoet.AnnotationSpec;
import com.squareup.javapoet.ClassName; import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.CodeBlock; import com.squareup.javapoet.CodeBlock;
import com.squareup.javapoet.JavaFile; import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.TypeSpec; import com.squareup.javapoet.TypeSpec;
import io.github.linpeilie.processor.AutoMapperProperties;
import java.io.IOException; import java.io.IOException;
import java.io.Writer; import java.io.Writer;
import javax.annotation.processing.ProcessingEnvironment; import javax.annotation.processing.ProcessingEnvironment;
@ -14,11 +15,11 @@ import static javax.tools.Diagnostic.Kind.ERROR;
public class MapperConfigGenerator { public class MapperConfigGenerator {
public void write(ProcessingEnvironment processingEnv) { public void write(ProcessingEnvironment processingEnv, String mapstructConfigName, String adapterClassName) {
try (final Writer writer = processingEnv.getFiler() try (final Writer writer = processingEnv.getFiler()
.createSourceFile(AutoMapperProperties.getConfigPackage() + "." + AutoMapperProperties.getConfigClassName()) .createSourceFile(AutoMapperProperties.getConfigPackage() + "." + mapstructConfigName)
.openWriter()) { .openWriter()) {
JavaFile.builder(AutoMapperProperties.getConfigPackage(), createConfigTypeSpec()).build().writeTo(writer); JavaFile.builder(AutoMapperProperties.getConfigPackage(), createConfigTypeSpec(mapstructConfigName, adapterClassName)).build().writeTo(writer);
} catch (IOException e) { } catch (IOException e) {
processingEnv.getMessager() processingEnv.getMessager()
.printMessage(ERROR, .printMessage(ERROR,
@ -27,17 +28,17 @@ public class MapperConfigGenerator {
} }
} }
private TypeSpec createConfigTypeSpec() { private TypeSpec createConfigTypeSpec(final String mapstructConfigName, final String adapterClassName) {
return TypeSpec.interfaceBuilder(AutoMapperProperties.getConfigClassName()) return TypeSpec.interfaceBuilder(mapstructConfigName)
.addModifiers(Modifier.PUBLIC) .addModifiers(Modifier.PUBLIC)
.addAnnotation(buildMapperConfigAnnotationSpec()) .addAnnotation(buildMapperConfigAnnotationSpec(adapterClassName))
.build(); .build();
} }
private AnnotationSpec buildMapperConfigAnnotationSpec() { private AnnotationSpec buildMapperConfigAnnotationSpec(final String adapterClassName) {
CodeBlock.Builder usesCodeBuilder = CodeBlock.builder().add("{"); CodeBlock.Builder usesCodeBuilder = CodeBlock.builder().add("{");
usesCodeBuilder.add("$T.class", usesCodeBuilder.add("$T.class",
ClassName.get(AutoMapperProperties.getAdapterPackage(), AutoMapperProperties.getAdapterClassName())); ClassName.get(AutoMapperProperties.getAdapterPackage(), adapterClassName));
CodeBlock usesCodeBlock = usesCodeBuilder.add("}").build(); CodeBlock usesCodeBlock = usesCodeBuilder.add("}").build();
return AnnotationSpec.builder(ClassName.get("org.mapstruct", "MapperConfig")) return AnnotationSpec.builder(ClassName.get("org.mapstruct", "MapperConfig"))
.addMember("componentModel", .addMember("componentModel",

View File

@ -1,4 +1,4 @@
package io.github.linpeilie.processor; package io.github.linpeilie.processor.generator;
import com.squareup.javapoet.AnnotationSpec; import com.squareup.javapoet.AnnotationSpec;
import com.squareup.javapoet.ClassName; import com.squareup.javapoet.ClassName;
@ -6,43 +6,47 @@ import com.squareup.javapoet.FieldSpec;
import com.squareup.javapoet.MethodSpec; import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterSpec; import com.squareup.javapoet.ParameterSpec;
import com.squareup.javapoet.TypeSpec; 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.Collection;
import javax.lang.model.element.Modifier; import javax.lang.model.element.Modifier;
public class SpringAdapterMapperGenerator extends AbstractAdapterMapperGenerator { public class SpringAdapterMapperGenerator extends AbstractAdapterMapperGenerator {
@Override @Override
protected TypeSpec createTypeSpec(Collection<AdapterMethodMetadata> adapterMethods) { protected TypeSpec createTypeSpec(Collection<AbstractAdapterMethodMetadata> adapterMethods, String adapterClassName) {
TypeSpec.Builder adapterBuilder = TypeSpec.classBuilder( TypeSpec.Builder adapterBuilder = TypeSpec.classBuilder(ClassName.get(adapterPackage(), adapterClassName))
ClassName.get(AutoMapperProperties.getAdapterPackage(), AutoMapperProperties.getAdapterClassName()))
.addModifiers(Modifier.PUBLIC) .addModifiers(Modifier.PUBLIC)
.addAnnotation(ClassName.get("org.springframework.stereotype", "Component")); .addAnnotation(ClassName.get("org.springframework.stereotype", "Component"));
adapterMethods.forEach(adapterMethod -> adapterBuilder.addField(buildMapperField(adapterMethod.getMapper())) adapterMethods.stream().map(AbstractAdapterMethodMetadata::getMapper)
.addMethod(buildMapperSetterMethod(adapterMethod.getMapper())) .distinct()
.forEach(mapper -> adapterBuilder.addField(buildMapperField(mapper))
.addMethod(buildMapperSetterMethod(mapper)));
adapterMethods.forEach(adapterMethod -> adapterBuilder
.addMethod(buildProxyMethod(adapterMethod))); .addMethod(buildProxyMethod(adapterMethod)));
return adapterBuilder.build(); return adapterBuilder.build();
} }
private FieldSpec buildMapperField(ClassName mapper) { private FieldSpec buildMapperField(ClassName mapper) {
return FieldSpec.builder(mapper, return FieldSpec.builder(mapper, firstWordToLower(mapper.simpleName()), Modifier.PRIVATE).build();
firstWordToLower(mapper.simpleName()), Modifier.PRIVATE).build();
} }
private String firstWordToLower(String str) { private String firstWordToLower(String str) {
return str.substring(0, 1).toLowerCase() + str.substring(1); return str.substring(0, 1).toLowerCase() + str.substring(1);
} }
private MethodSpec buildProxyMethod(AdapterMethodMetadata adapterMethodMetadata) { private MethodSpec buildProxyMethod(AbstractAdapterMethodMetadata adapterMethodMetadata) {
ParameterSpec parameterSpec = ParameterSpec.builder(adapterMethodMetadata.getSource(), ParameterSpec parameterSpec = ParameterSpec.builder(adapterMethodMetadata.getSource(),
firstWordToLower(adapterMethodMetadata.getSource().simpleName())).build(); firstWordToLower(adapterMethodMetadata.getSource().simpleName())).build();
return MethodSpec.methodBuilder(firstWordToLower(adapterMethodMetadata.getSource().simpleName()) + "To" + return MethodSpec.methodBuilder(adapterMethodMetadata.getMethodName())
adapterMethodMetadata.getTarget().simpleName())
.addModifiers(Modifier.PUBLIC) .addModifiers(Modifier.PUBLIC)
.addParameter(parameterSpec) .addParameter(parameterSpec)
.returns(adapterMethodMetadata.getTarget()) .returns(adapterMethodMetadata.getReturn())
.addStatement("return $N.convert($N)", firstWordToLower(adapterMethodMetadata.getMapper().simpleName()), .addStatement("return $N.$N($N)", firstWordToLower(adapterMethodMetadata.getMapper().simpleName()),
adapterMethodMetadata.getMapperMethodName(),
firstWordToLower(adapterMethodMetadata.getSource().simpleName())) firstWordToLower(adapterMethodMetadata.getSource().simpleName()))
.build(); .build();
} }
@ -52,18 +56,18 @@ public class SpringAdapterMapperGenerator extends AbstractAdapterMapperGenerator
return MethodSpec.methodBuilder("set" + mapper.simpleName()) return MethodSpec.methodBuilder("set" + mapper.simpleName())
.addModifiers(Modifier.PUBLIC) .addModifiers(Modifier.PUBLIC)
.addParameter(parameterSpec) .addParameter(parameterSpec)
.addAnnotation(AnnotationSpec.builder( .addAnnotation(
ClassName.get("org.springframework.beans.factory.annotation", "Autowired")) AnnotationSpec.builder(ClassName.get("org.springframework.beans.factory.annotation", "Autowired"))
.build()) .build())
.addStatement("this.$N = $N", buildMapperField(mapper), parameterSpec) .addStatement("this.$N = $N", buildMapperField(mapper), parameterSpec)
.build(); .build();
} }
private ParameterSpec buildMapperSetterParameter(ClassName mapper) { private ParameterSpec buildMapperSetterParameter(ClassName mapper) {
return ParameterSpec.builder(mapper, return ParameterSpec.builder(mapper, firstWordToLower(mapper.simpleName()))
firstWordToLower(mapper.simpleName()))
.addAnnotation( .addAnnotation(
AnnotationSpec.builder(ClassName.get("org.springframework.context.annotation", "Lazy")).build()) AnnotationSpec.builder(ClassName.get("org.springframework.context.annotation", "Lazy")).build())
.build(); .build();
} }
} }

View File

@ -0,0 +1,30 @@
package io.github.linpeilie.processor.metadata;
import com.squareup.javapoet.ClassName;
public abstract class AbstractAdapterMethodMetadata {
public AbstractAdapterMethodMetadata(final ClassName source, ClassName mapper) {
this.source = source;
this.mapper = mapper;
}
protected final ClassName source;
protected final ClassName mapper;
public abstract String getMethodName();
public abstract ClassName getReturn();
public abstract String getMapperMethodName();
public ClassName getSource() {
return source;
}
public ClassName getMapper() {
return mapper;
}
}

View File

@ -0,0 +1,30 @@
package io.github.linpeilie.processor.metadata;
import com.squareup.javapoet.ClassName;
import io.github.linpeilie.processor.AutoMapperProperties;
import org.apache.commons.lang3.StringUtils;
public abstract class AbstractMapperMetadata {
protected ClassName sourceClassName;
public String mapperPackage() {
return StringUtils.isNotEmpty(AutoMapperProperties.getMapperPackage())
? AutoMapperProperties.getMapperPackage() : sourceClassName.packageName();
}
public abstract String mapperName();
public ClassName mapperClass() {
return ClassName.get(mapperPackage(), mapperName());
}
public ClassName getSourceClassName() {
return sourceClassName;
}
public void setSourceClassName(final ClassName sourceClassName) {
this.sourceClassName = sourceClassName;
}
}

View File

@ -0,0 +1,50 @@
package io.github.linpeilie.processor.metadata;
import com.squareup.javapoet.ClassName;
import java.lang.annotation.Target;
public class AdapterMapMethodMetadata extends AbstractAdapterMethodMetadata {
private final ClassName target;
private final String methodName;
private final String mapperMethodName;
public AdapterMapMethodMetadata(final ClassName source,
final ClassName target,
final ClassName mapper,
boolean objectConverter) {
super(source, mapper);
this.target = target;
if (objectConverter) {
methodName = "objectTo" + source.simpleName();
mapperMethodName = "convertByObj";
} else {
methodName = "mapTo" + source.simpleName();
mapperMethodName = "convert";
}
}
public static AdapterMapMethodMetadata newInstance(final ClassName source,
final ClassName target,
final ClassName mapper,
boolean objectConverter) {
return new AdapterMapMethodMetadata(source, target, mapper, objectConverter);
}
@Override
public String getMethodName() {
return methodName;
}
@Override
public ClassName getReturn() {
return target;
}
@Override
public String getMapperMethodName() {
return mapperMethodName;
}
}

View File

@ -1,39 +1,37 @@
package io.github.linpeilie.processor; package io.github.linpeilie.processor.metadata;
import com.squareup.javapoet.ClassName; import com.squareup.javapoet.ClassName;
public class AdapterMethodMetadata { 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) {
this.source = source; super(source, mapper);
this.target = target; this.target = target;
this.mapper = mapper;
} }
private final ClassName source;
private final ClassName target; private final ClassName target;
private final ClassName mapper;
public static AdapterMethodMetadata newInstance(ClassName source, ClassName target, ClassName mapper) { public static AdapterMethodMetadata newInstance(ClassName source, ClassName target, ClassName mapper) {
return new AdapterMethodMetadata(source, target, mapper); return new AdapterMethodMetadata(source, target, mapper);
} }
@Override
public String getMethodName() { public String getMethodName() {
return source.simpleName().substring(0, 1).toLowerCase() + source.simpleName().substring(1) + "To" + return source.simpleName().substring(0, 1).toLowerCase() + source.simpleName().substring(1) + "To" +
target.simpleName(); target.simpleName();
} }
public ClassName getSource() {
return source;
}
public ClassName getTarget() { public ClassName getTarget() {
return target; return target;
} }
public ClassName getMapper() { @Override
return mapper; public ClassName getReturn() {
return target;
}
@Override
public String getMapperMethodName() {
return "convert";
} }
} }

View File

@ -0,0 +1,13 @@
package io.github.linpeilie.processor.metadata;
import io.github.linpeilie.processor.AutoMapperProperties;
import org.apache.commons.lang3.StringUtils;
public class AutoMapMapperMetadata extends AutoMapperMetadata {
@Override
public String mapperPackage() {
return StringUtils.isNotEmpty(AutoMapperProperties.getMapperPackage())
? AutoMapperProperties.getMapperPackage() : getTargetClassName().packageName();
}
}

View File

@ -1,12 +1,11 @@
package io.github.linpeilie.processor; package io.github.linpeilie.processor.metadata;
import com.squareup.javapoet.ClassName; import com.squareup.javapoet.ClassName;
import java.util.List; import java.util.List;
import org.apache.commons.lang3.StringUtils;
public class AutoMapperMetadata { public class AutoMapperMetadata extends AbstractMapperMetadata {
private ClassName sourceClassName;
private ClassName targetClassName; private ClassName targetClassName;
@ -14,24 +13,16 @@ public class AutoMapperMetadata {
private List<AutoMappingMetadata> fieldMappingList; private List<AutoMappingMetadata> fieldMappingList;
public String mapperPackage() { private ClassName superClass;
return StringUtils.isNotEmpty(AutoMapperProperties.getMapperPackage())
? AutoMapperProperties.getMapperPackage() : sourceClassName.packageName(); private ClassName[] superGenerics;
}
private ClassName mapstructConfigClass;
public String mapperName() { public String mapperName() {
return sourceClassName.simpleName() + "To" + targetClassName.simpleName() + "Mapper"; return sourceClassName.simpleName() + "To" + targetClassName.simpleName() + "Mapper";
} }
public ClassName getSourceClassName() {
return sourceClassName;
}
public AutoMapperMetadata setSourceClassName(final ClassName sourceClassName) {
this.sourceClassName = sourceClassName;
return this;
}
public ClassName getTargetClassName() { public ClassName getTargetClassName() {
return targetClassName; return targetClassName;
} }
@ -58,4 +49,28 @@ public class AutoMapperMetadata {
this.fieldMappingList = fieldMappingList; this.fieldMappingList = fieldMappingList;
return this; return this;
} }
public ClassName getSuperClass() {
return superClass;
}
public ClassName[] getSuperGenerics() {
return superGenerics;
}
public void setSuperGenerics(final ClassName[] superGenerics) {
this.superGenerics = superGenerics;
}
public void setSuperClass(final ClassName superClass) {
this.superClass = superClass;
}
public ClassName getMapstructConfigClass() {
return mapstructConfigClass;
}
public void setMapstructConfigClass(final ClassName mapstructConfigClass) {
this.mapstructConfigClass = mapstructConfigClass;
}
} }

View File

@ -1,4 +1,4 @@
package io.github.linpeilie.processor; package io.github.linpeilie.processor.metadata;
public class AutoMappingMetadata { public class AutoMappingMetadata {

View File

@ -1,6 +1,7 @@
package io.github.linpeilie.mapstruct; package io.github.linpeilie.mapstruct;
import io.github.linpeilie.AbstractCachedConverterFactory; import io.github.linpeilie.AbstractCachedConverterFactory;
import io.github.linpeilie.BaseMapMapper;
import io.github.linpeilie.BaseMapper; import io.github.linpeilie.BaseMapper;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.core.ResolvableType; import org.springframework.core.ResolvableType;
@ -23,4 +24,15 @@ public class SpringConverterFactory extends AbstractCachedConverterFactory {
} }
return (BaseMapper<S, T>) applicationContext.getBean(beanNames[0]); return (BaseMapper<S, T>) applicationContext.getBean(beanNames[0]);
} }
@Override
protected <S> BaseMapMapper findMapMapper(final Class<?> source) {
ResolvableType type = ResolvableType.forClassWithGenerics(
BaseMapMapper.class, source);
String[] beanNames = applicationContext.getBeanNamesForType(type);
if (beanNames.length == 0) {
return null;
}
return (BaseMapMapper) applicationContext.getBean(beanNames[0]);
}
} }

View File

@ -23,6 +23,10 @@
<groupId>org.mapstruct</groupId> <groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId> <artifactId>mapstruct</artifactId>
</dependency> </dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-core</artifactId>
</dependency>
</dependencies> </dependencies>
</project> </project>

View File

@ -6,6 +6,8 @@ public abstract class AbstractCachedConverterFactory implements ConverterFactory
private final ConcurrentHashMap<String, BaseMapper> mapperMap = new ConcurrentHashMap<>(); private final ConcurrentHashMap<String, BaseMapper> mapperMap = new ConcurrentHashMap<>();
private final ConcurrentHashMap<String, BaseMapMapper> mapMapperMap = new ConcurrentHashMap<>();
@Override @Override
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public <S, T> BaseMapper<S, T> getMapper(final Class<S> sourceType, final Class<T> targetType) { public <S, T> BaseMapper<S, T> getMapper(final Class<S> sourceType, final Class<T> targetType) {
@ -21,8 +23,24 @@ public abstract class AbstractCachedConverterFactory implements ConverterFactory
return null; return null;
} }
@Override
public <S> BaseMapMapper<S> getMapMapper(final Class<S> sourceType) {
final String key = sourceType.getName();
if (mapMapperMap.contains(key)) {
return mapMapperMap.get(key);
}
final BaseMapMapper mapper = findMapMapper(sourceType);
if (mapper != null) {
mapMapperMap.put(key, mapper);
return mapper;
}
return null;
}
protected abstract <S, T> BaseMapper findMapper(final Class<S> source, final Class<T> target); protected abstract <S, T> BaseMapper findMapper(final Class<S> source, final Class<T> target);
protected abstract <S> BaseMapMapper findMapMapper(final Class<?> source);
private String key(Class<?> source, Class<?> target) { private String key(Class<?> source, Class<?> target) {
return source.getName() + "__" + target.getName(); return source.getName() + "__" + target.getName();
} }

View File

@ -0,0 +1,19 @@
package io.github.linpeilie;
import java.util.Map;
public interface BaseMapMapper<T> {
T convert(Map<String, Object> map);
default T convertByObj(Object obj) {
if (obj == null) {
return null;
}
if (obj instanceof Map) {
return convert((Map<String, Object>) obj);
}
return null;
}
}

View File

@ -2,6 +2,8 @@ package io.github.linpeilie;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors; import java.util.stream.Collectors;
public class Converter { public class Converter {
@ -52,4 +54,19 @@ public class Converter {
return source.stream().map(item -> convert(item, targetType)).collect(Collectors.toList()); return source.stream().map(item -> convert(item, targetType)).collect(Collectors.toList());
} }
public <T> T convert(Map<String, Object> map, Class<T> target) {
if (map == null || map.isEmpty()) {
return null;
}
if (map.values().stream().allMatch(Objects::isNull)) {
return null;
}
final BaseMapMapper<T> mapper = converterFactory.getMapMapper(target);
if (mapper != null) {
return mapper.convert(map);
}
throw new ConvertException("cannot find converter from " + map.getClass().getName() + " to " +
target.getClass().getSimpleName());
}
} }

View File

@ -4,4 +4,6 @@ public interface ConverterFactory {
<S, T> BaseMapper<S, T> getMapper(Class<S> sourceType, Class<T> targetType); <S, T> BaseMapper<S, T> getMapper(Class<S> sourceType, Class<T> targetType);
<S> BaseMapMapper<S> getMapMapper(Class<S> sourceType);
} }

View File

@ -83,6 +83,10 @@ public class DefaultConverterFactory extends AbstractCachedConverterFactory {
return source.getSimpleName() + "To" + target.getSimpleName() + "Mapper"; return source.getSimpleName() + "To" + target.getSimpleName() + "Mapper";
} }
private String getMapMapperClassName(Class<?> source) {
return "MapTo" + source.getSimpleName() + "Mapper";
}
private String getMapperPackage(Class<?> sourceType) { private String getMapperPackage(Class<?> sourceType) {
return basePackage != null && !basePackage.isEmpty() ? basePackage return basePackage != null && !basePackage.isEmpty() ? basePackage
: sourceType.getPackage().getName(); : sourceType.getPackage().getName();
@ -100,4 +104,14 @@ public class DefaultConverterFactory extends AbstractCachedConverterFactory {
} }
} }
@Override
protected <S> BaseMapMapper findMapMapper(final Class<?> source) {
final String mapperClassName = getMapMapperClassName(source);
try {
final Class<?> mapperClass = Class.forName(getMapperPackage(source) + "." + mapperClassName);
return (BaseMapMapper) Mappers.getMapper(mapperClass);
} catch (ClassNotFoundException e) {
return null;
}
}
} }

View File

@ -0,0 +1,18 @@
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;
/**
* 为添加当前注解的类生成 Map 转为当前类的转换接口
*
* @author linpl
* @since 1.1.0
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface AutoMapMapper {
}

View File

@ -0,0 +1,81 @@
package io.github.linpeilie.map;
import cn.hutool.core.convert.Convert;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.URI;
import java.net.URL;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.Calendar;
import java.util.Currency;
import java.util.Date;
public class MapObjectConvert {
public static String objToString(Object obj) {
return Convert.toStr(obj);
}
public static BigDecimal objToBigDecimal(Object obj) {
return Convert.toBigDecimal(obj);
}
public static BigInteger objToBigInteger(Object obj) {
return Convert.toBigInteger(obj);
}
public static Integer objToInteger(Object obj) {
return Convert.toInt(obj);
}
public static Long objToLong(Object obj) {
return Convert.toLong(obj);
}
public static Double objToDouble(Object obj) {
return Convert.toDouble(obj);
}
public static Number objToNumber(Object obj) {
return Convert.toNumber(obj);
}
public static Boolean objToBoolean(Object obj) {
return Convert.toBool(obj);
}
public static Date objToDate(Object obj) {
return Convert.toDate(obj);
}
public static LocalDateTime objToLocalDateTime(Object obj) {
return Convert.toLocalDateTime(obj);
}
public static LocalDate objToLocalDate(Object obj) {
return Convert.convert(LocalDate.class, obj);
}
public static LocalTime objToLocalTime(Object obj) {
return Convert.convert(LocalTime.class, obj);
}
public static URI objToUri(Object obj) {
return Convert.convert(URI.class, obj);
}
public static URL objToUrl(Object obj) {
return Convert.convert(URL.class, obj);
}
public static Calendar objToCalendar(Object obj) {
return Convert.convert(Calendar.class, obj);
}
public static Currency objToCurrency(Object obj) {
return Convert.convert(Currency.class, obj);
}
}

11
pom.xml
View File

@ -17,11 +17,12 @@
</modules> </modules>
<properties> <properties>
<mapstruct-plus.version>1.0.0</mapstruct-plus.version> <mapstruct-plus.version>1.1.0</mapstruct-plus.version>
<maven.compiler.source>8</maven.compiler.source> <maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target> <maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<mapstruct.version>1.5.1.Final</mapstruct.version> <mapstruct.version>1.5.1.Final</mapstruct.version>
<hutool.version>5.8.9</hutool.version>
<projectUrl>https://github.com/linpeilie/mapstruct-plus.git</projectUrl> <projectUrl>https://github.com/linpeilie/mapstruct-plus.git</projectUrl>
</properties> </properties>
@ -62,6 +63,11 @@
<artifactId>spring-core</artifactId> <artifactId>spring-core</artifactId>
<version>5.3.23</version> <version>5.3.23</version>
</dependency> </dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-core</artifactId>
<version>${hutool.version}</version>
</dependency>
</dependencies> </dependencies>
</dependencyManagement> </dependencyManagement>
@ -110,6 +116,9 @@
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId> <artifactId>maven-javadoc-plugin</artifactId>
<version>2.9.1</version> <version>2.9.1</version>
<configuration>
<javadocExecutable>${java.home}/../bin/javadoc</javadocExecutable>
</configuration>
<executions> <executions>
<execution> <execution>
<id>attach-javadocs</id> <id>attach-javadocs</id>