commit 03b6ae24919cd1afa1dfda131a746342b981fd49 Author: linpl Date: Wed Feb 22 10:26:20 2023 +0800 init project diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..30ef8ed --- /dev/null +++ b/.gitignore @@ -0,0 +1,40 @@ +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### IntelliJ IDEA ### +.idea/modules.xml +.idea/jarRepositories.xml +.idea/compiler.xml +.idea/libraries/ +*.iws +*.iml +*.ipr + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store + +.idea \ No newline at end of file diff --git a/example/pom.xml b/example/pom.xml new file mode 100644 index 0000000..3b7ef7f --- /dev/null +++ b/example/pom.xml @@ -0,0 +1,79 @@ + + + + org.springframework.boot + spring-boot-starter-parent + 2.6.7 + + + 4.0.0 + + io.github.linpl + example + 1.0.0 + + + 8 + 8 + UTF-8 + 1.5.1.Final + 1.0.0 + + + + + org.springframework.boot + spring-boot-starter-web + + + io.github.linpl + mapstruct-plus-spring-boot-starter + ${mapstruct-plus.version} + + + org.mapstruct + mapstruct + ${mapstruct.version} + + + io.github.linpl + mapstruct-plus-processor + ${mapstruct-plus.version} + + + org.mapstruct + mapstruct-processor + ${mapstruct.version} + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + 1.8 + 1.8 + + + org.mapstruct + mapstruct-processor + ${mapstruct.version} + + + io.github.linpl + mapstruct-plus-processor + ${mapstruct-plus.version} + + + + + + + + + \ No newline at end of file diff --git a/example/src/main/java/io/github/linpl/mapstruct/Application.java b/example/src/main/java/io/github/linpl/mapstruct/Application.java new file mode 100644 index 0000000..f7c28b0 --- /dev/null +++ b/example/src/main/java/io/github/linpl/mapstruct/Application.java @@ -0,0 +1,17 @@ +package io.github.linpl.mapstruct; + +import io.github.linpl.annotations.AutoMapping; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.core.convert.ConversionService; + +@SpringBootApplication +public class Application { + + + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } + +} diff --git a/example/src/main/java/io/github/linpl/mapstruct/MapstructConfiguration.java b/example/src/main/java/io/github/linpl/mapstruct/MapstructConfiguration.java new file mode 100644 index 0000000..b83cf51 --- /dev/null +++ b/example/src/main/java/io/github/linpl/mapstruct/MapstructConfiguration.java @@ -0,0 +1,7 @@ +package io.github.linpl.mapstruct; + +import io.github.linpl.annotations.MapperConfig; + +@MapperConfig(mapperPackage = "io.github.linpl.mapstruct.convert") +public class MapstructConfiguration { +} diff --git a/example/src/main/java/io/github/linpl/mapstruct/Startup.java b/example/src/main/java/io/github/linpl/mapstruct/Startup.java new file mode 100644 index 0000000..47f4e9c --- /dev/null +++ b/example/src/main/java/io/github/linpl/mapstruct/Startup.java @@ -0,0 +1,29 @@ +package io.github.linpl.mapstruct; + +import io.github.linpl.Converter; +import io.github.linpl.mapstruct.dto.Car; +import io.github.linpl.mapstruct.dto.CarDto; +import io.github.linpl.mapstruct.dto.CarType; +import javax.annotation.PostConstruct; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.convert.ConversionService; +import org.springframework.stereotype.Component; + +@Component +public class Startup { + + private Converter converter; + + @PostConstruct + public void start() { + Car car = new Car(); + car.setType(CarType.OTHER); + CarDto catDto = converter.convert(car, CarDto.class); + System.out.println(catDto); + } + + @Autowired + public void setConverter(final Converter converter) { + this.converter = converter; + } +} diff --git a/example/src/main/java/io/github/linpl/mapstruct/dto/Car.java b/example/src/main/java/io/github/linpl/mapstruct/dto/Car.java new file mode 100644 index 0000000..7fac151 --- /dev/null +++ b/example/src/main/java/io/github/linpl/mapstruct/dto/Car.java @@ -0,0 +1,47 @@ +package io.github.linpl.mapstruct.dto; + +import io.github.linpl.annotations.AutoMapper; +import io.github.linpl.annotations.AutoMapping; + +@AutoMapper(target = CarDto.class) +public class Car { + + private String make; + private SeatConfiguration seatConfiguration; + private CarType type; + + @AutoMapping(target = "wheels", ignore = true) + private Wheels wheels; + + public String getMake() { + return make; + } + + public void setMake(final String make) { + this.make = make; + } + + public SeatConfiguration getSeatConfiguration() { + return seatConfiguration; + } + + public void setSeatConfiguration(final SeatConfiguration seatConfiguration) { + this.seatConfiguration = seatConfiguration; + } + + public CarType getType() { + return type; + } + + public void setType(final CarType type) { + this.type = type; + } + + public Wheels getWheels() { + return wheels; + } + + public void setWheels(Wheels wheels) { + this.wheels = wheels; + } +} diff --git a/example/src/main/java/io/github/linpl/mapstruct/dto/CarDto.java b/example/src/main/java/io/github/linpl/mapstruct/dto/CarDto.java new file mode 100644 index 0000000..686bbec --- /dev/null +++ b/example/src/main/java/io/github/linpl/mapstruct/dto/CarDto.java @@ -0,0 +1,52 @@ +package io.github.linpl.mapstruct.dto; + +import java.util.List; + +public class CarDto { + private String make; + private SeatConfigurationDto seatConfiguration; + private String type; + private List wheels; + + public String getMake() { + return make; + } + + public void setMake(final String make) { + this.make = make; + } + + public SeatConfigurationDto getSeatConfiguration() { + return seatConfiguration; + } + + public void setSeatConfiguration(final SeatConfigurationDto seatConfiguration) { + this.seatConfiguration = seatConfiguration; + } + + public String getType() { + return type; + } + + public void setType(final String type) { + this.type = type; + } + + public List getWheels() { + return wheels; + } + + public void setWheels(List wheels) { + this.wheels = wheels; + } + + @Override + public String toString() { + return "CarDto{" + + "make='" + make + '\'' + + ", seats=" + seatConfiguration + + ", type='" + type + '\'' + + ", wheels=" + wheels + + '}'; + } +} diff --git a/example/src/main/java/io/github/linpl/mapstruct/dto/CarType.java b/example/src/main/java/io/github/linpl/mapstruct/dto/CarType.java new file mode 100644 index 0000000..c125ae6 --- /dev/null +++ b/example/src/main/java/io/github/linpl/mapstruct/dto/CarType.java @@ -0,0 +1,5 @@ +package io.github.linpl.mapstruct.dto; + +public enum CarType { + SPORTS, OTHER +} diff --git a/example/src/main/java/io/github/linpl/mapstruct/dto/SeatConfiguration.java b/example/src/main/java/io/github/linpl/mapstruct/dto/SeatConfiguration.java new file mode 100644 index 0000000..210577b --- /dev/null +++ b/example/src/main/java/io/github/linpl/mapstruct/dto/SeatConfiguration.java @@ -0,0 +1,25 @@ +package io.github.linpl.mapstruct.dto; + +import io.github.linpl.annotations.AutoMapper; + +@AutoMapper(target = SeatConfigurationDto.class) +public class SeatConfiguration { + private int numberOfSeats; + private SeatMaterial seatMaterial; + + public SeatMaterial getSeatMaterial() { + return seatMaterial; + } + + public void setSeatMaterial(final SeatMaterial seatMaterial) { + this.seatMaterial = seatMaterial; + } + + public int getNumberOfSeats() { + return numberOfSeats; + } + + public void setNumberOfSeats(final int numberOfSeats) { + this.numberOfSeats = numberOfSeats; + } +} diff --git a/example/src/main/java/io/github/linpl/mapstruct/dto/SeatConfigurationDto.java b/example/src/main/java/io/github/linpl/mapstruct/dto/SeatConfigurationDto.java new file mode 100644 index 0000000..1129677 --- /dev/null +++ b/example/src/main/java/io/github/linpl/mapstruct/dto/SeatConfigurationDto.java @@ -0,0 +1,22 @@ +package io.github.linpl.mapstruct.dto; + +public class SeatConfigurationDto { + private int seatCount; + private String material; + + public int getSeatCount() { + return seatCount; + } + + public void setSeatCount(final int seatCount) { + this.seatCount = seatCount; + } + + public String getMaterial() { + return material; + } + + public void setMaterial(final String material) { + this.material = material; + } +} diff --git a/example/src/main/java/io/github/linpl/mapstruct/dto/SeatMaterial.java b/example/src/main/java/io/github/linpl/mapstruct/dto/SeatMaterial.java new file mode 100644 index 0000000..c05e2f0 --- /dev/null +++ b/example/src/main/java/io/github/linpl/mapstruct/dto/SeatMaterial.java @@ -0,0 +1,5 @@ +package io.github.linpl.mapstruct.dto; + +public enum SeatMaterial { + LEATHER, FABRIC +} diff --git a/example/src/main/java/io/github/linpl/mapstruct/dto/Wheel.java b/example/src/main/java/io/github/linpl/mapstruct/dto/Wheel.java new file mode 100644 index 0000000..d09e438 --- /dev/null +++ b/example/src/main/java/io/github/linpl/mapstruct/dto/Wheel.java @@ -0,0 +1,39 @@ +package io.github.linpl.mapstruct.dto; + +import io.github.linpl.annotations.AutoMapper; +import java.util.Objects; + +@AutoMapper(target = WheelDto.class) +public class Wheel { + private WheelPosition position; + private int diameter; + + public WheelPosition getPosition() { + return position; + } + + public void setPosition(WheelPosition position) { + this.position = position; + } + + public int getDiameter() { + return diameter; + } + + public void setDiameter(int diameter) { + this.diameter = diameter; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Wheel wheel = (Wheel) o; + return diameter == wheel.diameter && position == wheel.position; + } + + @Override + public int hashCode() { + return Objects.hash(position, diameter); + } +} diff --git a/example/src/main/java/io/github/linpl/mapstruct/dto/WheelDto.java b/example/src/main/java/io/github/linpl/mapstruct/dto/WheelDto.java new file mode 100644 index 0000000..3b828b2 --- /dev/null +++ b/example/src/main/java/io/github/linpl/mapstruct/dto/WheelDto.java @@ -0,0 +1,37 @@ +package io.github.linpl.mapstruct.dto; + +import java.util.Objects; + +public class WheelDto { + private String position; + private int diameter; + + public void setDiameter(int diameter) { + this.diameter = diameter; + } + + public String getPosition() { + return position; + } + + public void setPosition(String position) { + this.position = position; + } + + public int getDiameter() { + return diameter; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + WheelDto wheelDto = (WheelDto) o; + return diameter == wheelDto.diameter && Objects.equals(position, wheelDto.position); + } + + @Override + public int hashCode() { + return Objects.hash(position, diameter); + } +} diff --git a/example/src/main/java/io/github/linpl/mapstruct/dto/WheelPosition.java b/example/src/main/java/io/github/linpl/mapstruct/dto/WheelPosition.java new file mode 100644 index 0000000..bde7419 --- /dev/null +++ b/example/src/main/java/io/github/linpl/mapstruct/dto/WheelPosition.java @@ -0,0 +1,8 @@ +package io.github.linpl.mapstruct.dto; + +public enum WheelPosition { + LEFT_FRONT, + RIGHT_FRONT, + LEFT_REAR, + RIGHT_REAR +} diff --git a/example/src/main/java/io/github/linpl/mapstruct/dto/Wheels.java b/example/src/main/java/io/github/linpl/mapstruct/dto/Wheels.java new file mode 100644 index 0000000..5aac064 --- /dev/null +++ b/example/src/main/java/io/github/linpl/mapstruct/dto/Wheels.java @@ -0,0 +1,38 @@ +package io.github.linpl.mapstruct.dto; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Spliterator; +import java.util.function.Consumer; + +public class Wheels implements Iterable { + private List wheelsList = new ArrayList<>(); + + public List getWheelsList() { + return wheelsList; + } + + public void setWheelsList(List wheelsList) { + this.wheelsList = wheelsList; + } + + public void add(final Wheel wheel) { + wheelsList.add(wheel); + } + + @Override + public Iterator iterator() { + return getWheelsList().iterator(); + } + + @Override + public void forEach(Consumer action) { + getWheelsList().forEach(action); + } + + @Override + public Spliterator spliterator() { + return getWheelsList().spliterator(); + } +} diff --git a/mapstruct-plus-core/pom.xml b/mapstruct-plus-core/pom.xml new file mode 100644 index 0000000..dd7efc6 --- /dev/null +++ b/mapstruct-plus-core/pom.xml @@ -0,0 +1,29 @@ + + + + mapstruct-plus + io.github.linpl + ${mapstruct-plus.version} + ../pom.xml + + 4.0.0 + + mapstruct-plus-core + + + 8 + 8 + UTF-8 + + + + + org.mapstruct + mapstruct + true + + + + \ No newline at end of file diff --git a/mapstruct-plus-core/src/main/java/io/github/linpl/BaseMapper.java b/mapstruct-plus-core/src/main/java/io/github/linpl/BaseMapper.java new file mode 100644 index 0000000..c07bf92 --- /dev/null +++ b/mapstruct-plus-core/src/main/java/io/github/linpl/BaseMapper.java @@ -0,0 +1,11 @@ +package io.github.linpl; + +import org.mapstruct.MappingTarget; + +public interface BaseMapper { + + T convert(S source); + + T convert(S source, @MappingTarget T target); + +} diff --git a/mapstruct-plus-core/src/main/java/io/github/linpl/ConvertException.java b/mapstruct-plus-core/src/main/java/io/github/linpl/ConvertException.java new file mode 100644 index 0000000..5267f02 --- /dev/null +++ b/mapstruct-plus-core/src/main/java/io/github/linpl/ConvertException.java @@ -0,0 +1,12 @@ +package io.github.linpl; + +public class ConvertException extends RuntimeException { + + public ConvertException(final String message) { + super(message); + } + + public ConvertException(final String message, final Throwable cause) { + super(message, cause); + } +} diff --git a/mapstruct-plus-core/src/main/java/io/github/linpl/Converter.java b/mapstruct-plus-core/src/main/java/io/github/linpl/Converter.java new file mode 100644 index 0000000..0694c3e --- /dev/null +++ b/mapstruct-plus-core/src/main/java/io/github/linpl/Converter.java @@ -0,0 +1,51 @@ +package io.github.linpl; + +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +public class Converter { + + private final ConverterFactory converterFactory; + + public Converter(final ConverterFactory converterFactory) { + this.converterFactory = converterFactory; + } + + @SuppressWarnings("unchecked") + public T convert(S source, Class targetType) { + if (source == null) { + return null; + } + BaseMapper mapper = (BaseMapper) converterFactory.getMapper(source.getClass(), targetType); + if (mapper != null) { + return mapper.convert(source); + } + throw new ConvertException( + "cannot find converter from " + source.getClass().getSimpleName() + " to " + targetType.getSimpleName()); + } + + @SuppressWarnings("unchecked") + public T convert(S source, T target) { + if (source == null) { + return null; + } + if (target == null) { + return null; + } + BaseMapper mapper = (BaseMapper) converterFactory.getMapper(source.getClass(), target.getClass()); + if (mapper != null) { + return mapper.convert(source); + } + throw new ConvertException("cannot find converter from " + source.getClass().getSimpleName() + " to " + + target.getClass().getSimpleName()); + } + + public List convert(List source, Class targetType) { + if (source == null || source.size() == 0) { + return Collections.emptyList(); + } + return source.stream().map(item -> convert(item, targetType)).collect(Collectors.toList()); + } + +} diff --git a/mapstruct-plus-core/src/main/java/io/github/linpl/ConverterFactory.java b/mapstruct-plus-core/src/main/java/io/github/linpl/ConverterFactory.java new file mode 100644 index 0000000..370804e --- /dev/null +++ b/mapstruct-plus-core/src/main/java/io/github/linpl/ConverterFactory.java @@ -0,0 +1,7 @@ +package io.github.linpl; + +public interface ConverterFactory { + + BaseMapper getMapper(Class sourceType, Class targetType); + +} diff --git a/mapstruct-plus-core/src/main/java/io/github/linpl/annotations/AutoMapper.java b/mapstruct-plus-core/src/main/java/io/github/linpl/annotations/AutoMapper.java new file mode 100644 index 0000000..623aee0 --- /dev/null +++ b/mapstruct-plus-core/src/main/java/io/github/linpl/annotations/AutoMapper.java @@ -0,0 +1,18 @@ +package io.github.linpl.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.CLASS) +public @interface AutoMapper { + + Class target(); + + Class[] uses() default {}; + + Class[] imports() default {}; + +} diff --git a/mapstruct-plus-core/src/main/java/io/github/linpl/annotations/AutoMapping.java b/mapstruct-plus-core/src/main/java/io/github/linpl/annotations/AutoMapping.java new file mode 100644 index 0000000..39de53d --- /dev/null +++ b/mapstruct-plus-core/src/main/java/io/github/linpl/annotations/AutoMapping.java @@ -0,0 +1,24 @@ +package io.github.linpl.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.CLASS) +public @interface AutoMapping { + + String target(); + + String dateFormat() default ""; + + String numberFormat() default ""; + + String constant() default ""; + + String expression() default ""; + + boolean ignore() default false; + +} diff --git a/mapstruct-plus-core/src/main/java/io/github/linpl/annotations/MapperConfig.java b/mapstruct-plus-core/src/main/java/io/github/linpl/annotations/MapperConfig.java new file mode 100644 index 0000000..d4a8300 --- /dev/null +++ b/mapstruct-plus-core/src/main/java/io/github/linpl/annotations/MapperConfig.java @@ -0,0 +1,24 @@ +package io.github.linpl.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 在一个类或者接口上添加该注释,作为自动生成 Mapper 的配置类。在一个模块中,只能有一个有该注释的类。 + * Marks a class or interface as configuration source. There can be only one annotated type in + * each compiled module. + * + * @author linpl + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.SOURCE) +public @interface MapperConfig { + + /** + * 所生成的 Mapper 接口的包 + */ + String mapperPackage() default ""; + +} diff --git a/mapstruct-plus-processor/pom.xml b/mapstruct-plus-processor/pom.xml new file mode 100644 index 0000000..0b4dd2d --- /dev/null +++ b/mapstruct-plus-processor/pom.xml @@ -0,0 +1,52 @@ + + + + mapstruct-plus + io.github.linpl + ${mapstruct-plus.version} + ../pom.xml + + 4.0.0 + + mapstruct-plus-processor + + + 8 + 8 + UTF-8 + + + + + io.github.linpl + mapstruct-plus-core + + + com.baidu.lbsyun + javapoet + + + org.apache.commons + commons-lang3 + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + 1.8 + 1.8 + UTF-8 + none + + + + + + \ No newline at end of file diff --git a/mapstruct-plus-processor/src/main/java/io/github/linpl/processor/AdapterMapperGenerator.java b/mapstruct-plus-processor/src/main/java/io/github/linpl/processor/AdapterMapperGenerator.java new file mode 100644 index 0000000..6633a98 --- /dev/null +++ b/mapstruct-plus-processor/src/main/java/io/github/linpl/processor/AdapterMapperGenerator.java @@ -0,0 +1,89 @@ +package io.github.linpl.processor; + +import com.squareup.javapoet.AnnotationSpec; +import com.squareup.javapoet.ClassName; +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 static javax.tools.Diagnostic.Kind.ERROR; + +public class AdapterMapperGenerator { + + public void write(ProcessingEnvironment processingEnv, Collection adapterMethods) { + // write Adapter + try (final Writer writer = processingEnv.getFiler() + .createSourceFile( + AutoMapperProperties.getAdapterPackage() + "." + AutoMapperProperties.getAdapterClassName()) + .openWriter()) { + JavaFile.builder(AutoMapperProperties.getAdapterPackage(), createTypeSpec(adapterMethods)) + .build() + .writeTo(writer); + } catch (IOException e) { + processingEnv.getMessager() + .printMessage(ERROR, + "Error while opening " + AutoMapperProperties.getAdapterClassName() + " output file: " + + e.getMessage()); + } + } + + private TypeSpec createTypeSpec(Collection adapterMethods) { + TypeSpec.Builder adapterBuilder = TypeSpec.classBuilder( + ClassName.get(AutoMapperProperties.getAdapterPackage(), AutoMapperProperties.getAdapterClassName())) + .addModifiers(Modifier.PUBLIC) + .addAnnotation(ClassName.get("org.springframework.stereotype", "Component")); + + adapterMethods.forEach(adapterMethod -> adapterBuilder.addField(buildMapperField(adapterMethod.getMapper())) + .addMethod(buildMapperSetterMethod(adapterMethod.getMapper())) + .addMethod(buildProxyMethod(adapterMethod))); + + return adapterBuilder.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 buildProxyMethod(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()), + 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())) + .addAnnotation( + AnnotationSpec.builder(ClassName.get("org.springframework.context.annotation", "Lazy")).build()) + .build(); + } + +} diff --git a/mapstruct-plus-processor/src/main/java/io/github/linpl/processor/AdapterMethodMetadata.java b/mapstruct-plus-processor/src/main/java/io/github/linpl/processor/AdapterMethodMetadata.java new file mode 100644 index 0000000..8eabc78 --- /dev/null +++ b/mapstruct-plus-processor/src/main/java/io/github/linpl/processor/AdapterMethodMetadata.java @@ -0,0 +1,39 @@ +package io.github.linpl.processor; + +import com.squareup.javapoet.ClassName; + +public class AdapterMethodMetadata { + + private AdapterMethodMetadata(final ClassName source, final ClassName target, ClassName mapper) { + this.source = source; + this.target = target; + this.mapper = mapper; + } + + private final ClassName source; + + private final ClassName target; + + private final ClassName mapper; + + public static AdapterMethodMetadata newInstance(ClassName source, ClassName target, ClassName mapper) { + return new AdapterMethodMetadata(source, target, mapper); + } + + public String getMethodName() { + return source.simpleName().substring(0, 1).toLowerCase() + source.simpleName().substring(1) + "To" + + target.simpleName(); + } + + public ClassName getSource() { + return source; + } + + public ClassName getTarget() { + return target; + } + + public ClassName getMapper() { + return mapper; + } +} diff --git a/mapstruct-plus-processor/src/main/java/io/github/linpl/processor/AutoMapperGenerator.java b/mapstruct-plus-processor/src/main/java/io/github/linpl/processor/AutoMapperGenerator.java new file mode 100644 index 0000000..1accd84 --- /dev/null +++ b/mapstruct-plus-processor/src/main/java/io/github/linpl/processor/AutoMapperGenerator.java @@ -0,0 +1,112 @@ +package io.github.linpl.processor; + +import com.squareup.javapoet.AnnotationSpec; +import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.CodeBlock; +import com.squareup.javapoet.JavaFile; +import com.squareup.javapoet.MethodSpec; +import com.squareup.javapoet.ParameterSpec; +import com.squareup.javapoet.ParameterizedTypeName; +import com.squareup.javapoet.TypeSpec; +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; +import java.util.stream.Collectors; +import javax.lang.model.element.Modifier; +import org.apache.commons.lang3.StringUtils; + +import static io.github.linpl.processor.Constants.*; + +public class AutoMapperGenerator { + + public void write(AutoMapperMetadata metadata, Writer writer) { + try { + JavaFile.builder(AutoMapperProperties.getMapperPackage(), createTypeSpec(metadata)).build().writeTo(writer); + } catch (IOException e) { + throw new UncheckedIOException(e); + } catch (Exception e) { + throw e; + } + } + + private TypeSpec createTypeSpec(AutoMapperMetadata metadata) { + ParameterizedTypeName converterName = + ParameterizedTypeName.get(ClassName.get(BASE_MAPPER_PACKAGE, BASE_MAPPER_CLASS_NAME), + metadata.getSourceClassName(), metadata.getTargetClassName()); + + TypeSpec.Builder builder = TypeSpec.interfaceBuilder(metadata.mapperName()) + .addSuperinterface(converterName) + .addModifiers(Modifier.PUBLIC) + .addAnnotation(buildGeneratedMapperAnnotationSpec(metadata)); + if (metadata.getFieldMappingList() != null && !metadata.getFieldMappingList().isEmpty()) { + final ParameterSpec source = ParameterSpec.builder(metadata.getSourceClassName(), "source").build(); + final ParameterSpec target = ParameterSpec.builder(metadata.getTargetClassName(), "target") + .addAnnotation(AnnotationSpec.builder(ClassName.get("org.mapstruct", "MappingTarget")).build()) + .build(); + builder.addMethod(addConvertMethodSpec(Collections.singletonList(source), metadata.getFieldMappingList(), + metadata.getTargetClassName())); + builder.addMethod(addConvertMethodSpec(Arrays.asList(source, target), metadata.getFieldMappingList(), + metadata.getTargetClassName())); + } + return builder.build(); + } + + private MethodSpec addConvertMethodSpec(List parameterSpecs, + List autoMappingMetadataList, + ClassName target) { + return MethodSpec.methodBuilder("convert") + .addParameters(parameterSpecs) + .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT) + .addAnnotations(buildMappingAnnotations(autoMappingMetadataList)) + .returns(target) + .build(); + } + + private List buildMappingAnnotations(final List autoMappingMetadataList) { + return autoMappingMetadataList.stream().map(autoMappingMetadata -> { + final AnnotationSpec.Builder builder = AnnotationSpec.builder(ClassName.get("org.mapstruct", "Mapping")) + .addMember("target", CodeBlock.builder().add("$S", autoMappingMetadata.getTarget()).build()) + + .addMember("dateFormat", + CodeBlock.builder().add("$S", autoMappingMetadata.getDateFormat()).build()) + .addMember("numberFormat", + CodeBlock.builder().add("$S", autoMappingMetadata.getNumberFormat()).build()) + .addMember("ignore", CodeBlock.builder().add(String.valueOf(autoMappingMetadata.isIgnore())).build()); + if (StringUtils.isNoneEmpty(autoMappingMetadata.getExpression())) { + builder.addMember("expression", + CodeBlock.builder().add("$S", autoMappingMetadata.getExpression()).build()); + } else { + builder.addMember("source", CodeBlock.builder().add("$S", autoMappingMetadata.getSource()).build()); + } + return builder.build(); + }).collect(Collectors.toList()); + } + + private AnnotationSpec buildGeneratedMapperAnnotationSpec(AutoMapperMetadata metadata) { + List usesClassNameList = + Optional.ofNullable(metadata.getUsesClassNameList()).orElse(new ArrayList<>()); + + // config + CodeBlock configCodeBlock = CodeBlock.builder() + .add("$T.class", + ClassName.get(AutoMapperProperties.getMapperPackage(), AutoMapperProperties.getConfigClassName())) + .build(); + + // uses + CodeBlock.Builder usesCodeBuilder = CodeBlock.builder().add("{"); + usesClassNameList.forEach(item -> usesCodeBuilder.add("$T.class", item)); + CodeBlock usesCodeBlock = usesCodeBuilder.add("}").build(); + + AnnotationSpec.Builder builder = + AnnotationSpec.builder(ClassName.get(MAPSTRUCT_MAPPER_PACKAGE, MAPSTRUCT_MAPPER_CLASS_NAME)) + .addMember("config", configCodeBlock) + .addMember("uses", usesCodeBlock); + return builder.build(); + } + +} diff --git a/mapstruct-plus-processor/src/main/java/io/github/linpl/processor/AutoMapperMetadata.java b/mapstruct-plus-processor/src/main/java/io/github/linpl/processor/AutoMapperMetadata.java new file mode 100644 index 0000000..2e72137 --- /dev/null +++ b/mapstruct-plus-processor/src/main/java/io/github/linpl/processor/AutoMapperMetadata.java @@ -0,0 +1,55 @@ +package io.github.linpl.processor; + +import com.squareup.javapoet.ClassName; +import java.util.List; + +public class AutoMapperMetadata { + + private ClassName sourceClassName; + + private ClassName targetClassName; + + private List usesClassNameList; + + private List fieldMappingList; + + public String mapperName() { + 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() { + return targetClassName; + } + + public AutoMapperMetadata setTargetClassName(final ClassName targetClassName) { + this.targetClassName = targetClassName; + return this; + } + + public List getUsesClassNameList() { + return usesClassNameList; + } + + public AutoMapperMetadata setUsesClassNameList(final List usesClassNameList) { + this.usesClassNameList = usesClassNameList; + return this; + } + + public List getFieldMappingList() { + return fieldMappingList; + } + + public AutoMapperMetadata setFieldMappingList(final List fieldMappingList) { + this.fieldMappingList = fieldMappingList; + return this; + } +} diff --git a/mapstruct-plus-processor/src/main/java/io/github/linpl/processor/AutoMapperProcessor.java b/mapstruct-plus-processor/src/main/java/io/github/linpl/processor/AutoMapperProcessor.java new file mode 100644 index 0000000..0de85bd --- /dev/null +++ b/mapstruct-plus-processor/src/main/java/io/github/linpl/processor/AutoMapperProcessor.java @@ -0,0 +1,238 @@ +package io.github.linpl.processor; + +import com.squareup.javapoet.ClassName; +import io.github.linpl.annotations.AutoMapper; +import io.github.linpl.annotations.AutoMapping; +import io.github.linpl.annotations.MapperConfig; +import java.io.IOException; +import java.io.Writer; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.function.Supplier; +import java.util.stream.Collectors; +import javax.annotation.processing.AbstractProcessor; +import javax.annotation.processing.RoundEnvironment; +import javax.annotation.processing.SupportedAnnotationTypes; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.MirroredTypeException; +import javax.lang.model.type.MirroredTypesException; +import javax.lang.model.type.TypeMirror; +import javax.tools.Diagnostic; +import org.apache.commons.lang3.StringUtils; + +import static io.github.linpl.processor.Constants.AUTO_MAPPER_ANNOTATION; +import static io.github.linpl.processor.Constants.MAPPER_CONFIG_ANNOTATION; +import static javax.tools.Diagnostic.Kind.ERROR; + +@SupportedAnnotationTypes({AUTO_MAPPER_ANNOTATION, MAPPER_CONFIG_ANNOTATION}) +public class AutoMapperProcessor extends AbstractProcessor { + + private final AutoMapperGenerator mapperGenerator; + + private final AdapterMapperGenerator adapterMapperGenerator; + + private final MapperConfigGenerator mapperConfigGenerator; + + private final Map methodMap = new HashMap<>(); + + private final Set mapperSet = new HashSet<>(); + + public AutoMapperProcessor() { + this.mapperGenerator = new AutoMapperGenerator(); + this.adapterMapperGenerator = new AdapterMapperGenerator(); + this.mapperConfigGenerator = new MapperConfigGenerator(); + } + + private boolean isAutoMapperAnnotation(TypeElement annotation) { + return AUTO_MAPPER_ANNOTATION.contentEquals(annotation.getQualifiedName()); + } + + private boolean isMapperConfigAnnotation(TypeElement annotation) { + return MAPPER_CONFIG_ANNOTATION.contentEquals(annotation.getQualifiedName()); + } + + @Override + public boolean process(final Set annotations, final RoundEnvironment roundEnv) { + boolean hasAutoMapper = annotations.stream().anyMatch(this::isAutoMapperAnnotation); + if (!hasAutoMapper) { + return false; + } + processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "start refresh properties"); + refreshProperties(annotations, roundEnv); + processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "start write config class"); + writeConfigClass(); + processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "start generate mapper class"); + annotations.stream() + .filter(this::isAutoMapperAnnotation) + .forEach(annotation -> processAutoMapperAnnotation(roundEnv, annotation)); + return false; + } + + private void refreshProperties(final Set annotations, final RoundEnvironment roundEnv) { + annotations.stream() + .filter(this::isMapperConfigAnnotation) + .findFirst() + .flatMap(annotation -> roundEnv.getElementsAnnotatedWith(annotation).stream().findFirst()) + .ifPresent(element -> { + final MapperConfig mapperConfig = element.getAnnotation(MapperConfig.class); + String mapperPackage = StringUtils.isEmpty(mapperConfig.mapperPackage()) ? getPackageName(element) + : mapperConfig.mapperPackage(); + processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "mapper package " + mapperPackage); + AutoMapperProperties.setMapperPackage(mapperPackage); + }); + } + + private String getPackageName(Element element) { + return String.valueOf(processingEnv.getElementUtils().getPackageOf(element).getQualifiedName()); + } + + private void writeConfigClass() { + mapperConfigGenerator.write(processingEnv); + } + + private void processAutoMapperAnnotation(final RoundEnvironment roundEnv, final TypeElement annotation) { + final List autoMapperMetadataList = roundEnv.getElementsAnnotatedWith(annotation) + .stream() + .map(this::buildAutoMapperMetadata) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + + autoMapperMetadataList.forEach(autoMapperMetadata -> mapperSet.add(autoMapperMetadata.mapperName())); + + List reverseMapperMetadataList = new ArrayList<>(); + + autoMapperMetadataList.forEach(autoMapperMetadata -> { + final AutoMapperMetadata reverseMapperMetadata = reverseMapper(autoMapperMetadata); + if (!mapperSet.add(reverseMapperMetadata.mapperName())) { + return; + } + reverseMapperMetadataList.add(reverseMapperMetadata); + }); + + autoMapperMetadataList.addAll(reverseMapperMetadataList); + + autoMapperMetadataList.forEach(this::writeAutoMapperClassFile); + + adapterMapperGenerator.write(processingEnv, methodMap.values()); + } + + private AutoMapperMetadata reverseMapper(AutoMapperMetadata autoMapperMetadata) { + AutoMapperMetadata reverseMapperMetadata = new AutoMapperMetadata(); + reverseMapperMetadata.setSourceClassName(autoMapperMetadata.getTargetClassName()); + reverseMapperMetadata.setTargetClassName(autoMapperMetadata.getSourceClassName()); + // 需要继承的属性 + final List fieldMetadataList = autoMapperMetadata.getFieldMappingList().stream() + .map(fieldMapping -> { + final AutoMappingMetadata autoMappingMetadata = new AutoMappingMetadata(); + autoMappingMetadata.setSource(fieldMapping.getTarget()); + autoMappingMetadata.setTarget(fieldMapping.getSource()); + return autoMappingMetadata; + }).collect(Collectors.toList()); + reverseMapperMetadata.setFieldMappingList(fieldMetadataList); + return reverseMapperMetadata; + } + + private void writeAutoMapperClassFile(AutoMapperMetadata metadata) { + String mapperPackage = AutoMapperProperties.getMapperPackage(); + String mapperClassName = metadata.mapperName(); + try (final Writer writer = processingEnv.getFiler() + .createSourceFile(mapperPackage + "." + mapperClassName) + .openWriter()) { + mapperGenerator.write(metadata, writer); + addAdapterMethod(metadata.getSourceClassName(), metadata.getTargetClassName(), + ClassName.get(mapperPackage, mapperClassName)); + } catch (IOException e) { + processingEnv.getMessager() + .printMessage(ERROR, + "Error while opening " + metadata.mapperName() + " output file: " + e.getMessage()); + } + } + + private void addAdapterMethod(ClassName source, ClassName target, ClassName mapper) { + AdapterMethodMetadata adapterMethodMetadata = AdapterMethodMetadata.newInstance(source, target, mapper); + methodMap.put(adapterMethodMetadata.getMethodName(), adapterMethodMetadata); + } + + private AutoMapperMetadata buildAutoMapperMetadata(final Element ele) { + AutoMapper autoMapperAnnotation = ele.getAnnotation(AutoMapper.class); + if (autoMapperAnnotation == null) { + return null; + } + + ClassName source = ClassName.get((TypeElement) ele); + ClassName target = transToClassName(autoMapperAnnotation::target); + if (target == null) { + return null; + } + List uses = transToClassNameList(autoMapperAnnotation::uses); + List autoMappingMetadataList = buildFieldMappingMetadata((TypeElement) ele); + + AutoMapperMetadata metadata = new AutoMapperMetadata(); + + metadata.setSourceClassName(source); + metadata.setTargetClassName(target); + metadata.setUsesClassNameList(uses); + metadata.setFieldMappingList(autoMappingMetadataList); + + return metadata; + } + + private List buildFieldMappingMetadata(final TypeElement autoMapperEle) { + List list = new ArrayList<>(); + + if (!autoMapperEle.getKind().isClass()) { + return list; + } + + for (Element ele : autoMapperEle.getEnclosedElements()) { + if (ele.getKind() != ElementKind.FIELD) { + continue; + } + AutoMapping autoMapping = ele.getAnnotation(AutoMapping.class); + if (autoMapping == null) { + continue; + } + + AutoMappingMetadata metadata = new AutoMappingMetadata(); + metadata.setTarget(autoMapping.target()); + metadata.setSource(ele.getSimpleName().toString()); + metadata.setIgnore(autoMapping.ignore()); + list.add(metadata); + } + + return list; + } + + private ClassName transToClassName(Supplier> classSupplier) { + TypeMirror typeMirror = null; + try { + Class targetClass = classSupplier.get(); + } catch (MirroredTypeException e) { + typeMirror = e.getTypeMirror(); + } + if (typeMirror == null) { + return null; + } + return (ClassName) ClassName.get(typeMirror); + } + + private List transToClassNameList(Supplier[]> classSuppler) { + List typeMirrors = null; + try { + Class[] classes = classSuppler.get(); + } catch (MirroredTypesException e) { + typeMirrors = e.getTypeMirrors(); + } + return typeMirrors.stream() + .map(typeMirror -> (ClassName) ClassName.get(typeMirror)) + .collect(Collectors.toList()); + } + +} diff --git a/mapstruct-plus-processor/src/main/java/io/github/linpl/processor/AutoMapperProperties.java b/mapstruct-plus-processor/src/main/java/io/github/linpl/processor/AutoMapperProperties.java new file mode 100644 index 0000000..749c738 --- /dev/null +++ b/mapstruct-plus-processor/src/main/java/io/github/linpl/processor/AutoMapperProperties.java @@ -0,0 +1,35 @@ +package io.github.linpl.processor; + +import static io.github.linpl.processor.Constants.*; + +public class AutoMapperProperties { + + + private static String mapperPackage = DEFAULT_MAPPER_PACKAGE; + + private static String adapterPackage = mapperPackage; + + private static String adapterClassName = DEFAULT_ADAPTER_CLASS_NAME; + + private static String configClassName = AUTO_MAPPER_CONFIG_CLASS_NAME; + + public static String getMapperPackage() { + return mapperPackage; + } + + public static String getAdapterPackage() { + return getMapperPackage(); + } + + public static String getAdapterClassName() { + return adapterClassName; + } + + public static String getConfigClassName() { + return configClassName; + } + + public static void setMapperPackage(final String mapperPackage) { + AutoMapperProperties.mapperPackage = mapperPackage; + } +} diff --git a/mapstruct-plus-processor/src/main/java/io/github/linpl/processor/AutoMappingMetadata.java b/mapstruct-plus-processor/src/main/java/io/github/linpl/processor/AutoMappingMetadata.java new file mode 100644 index 0000000..2264a8b --- /dev/null +++ b/mapstruct-plus-processor/src/main/java/io/github/linpl/processor/AutoMappingMetadata.java @@ -0,0 +1,64 @@ +package io.github.linpl.processor; + +public class AutoMappingMetadata { + + private String target = ""; + + private String source = ""; + + private String dateFormat = ""; + + private String numberFormat = ""; + + private String expression = ""; + + private boolean ignore = false; + + public String getTarget() { + return target; + } + + public void setTarget(final String target) { + this.target = target; + } + + public String getSource() { + return source; + } + + public void setSource(final String source) { + this.source = source; + } + + public String getDateFormat() { + return dateFormat; + } + + public void setDateFormat(final String dateFormat) { + this.dateFormat = dateFormat; + } + + public String getNumberFormat() { + return numberFormat; + } + + public void setNumberFormat(final String numberFormat) { + this.numberFormat = numberFormat; + } + + public String getExpression() { + return expression; + } + + public void setExpression(final String expression) { + this.expression = expression; + } + + public boolean isIgnore() { + return ignore; + } + + public void setIgnore(final boolean ignore) { + this.ignore = ignore; + } +} diff --git a/mapstruct-plus-processor/src/main/java/io/github/linpl/processor/Constants.java b/mapstruct-plus-processor/src/main/java/io/github/linpl/processor/Constants.java new file mode 100644 index 0000000..c8a3bf7 --- /dev/null +++ b/mapstruct-plus-processor/src/main/java/io/github/linpl/processor/Constants.java @@ -0,0 +1,27 @@ +package io.github.linpl.processor; + +public class Constants { + + private static final String DEFAULT_BASE_PACKAGE = AutoMapperProcessor.class.getPackage().getName(); + + public static final String DEFAULT_MAPPER_PACKAGE = DEFAULT_BASE_PACKAGE + ".mappers"; + + public static final String DEFAULT_ADAPTER_CLASS_NAME = "ConvertMapperAdapter"; + + + public static final String AUTO_MAPPER_CONFIG_CLASS_NAME = "AutoMapperConfig"; + + public static final String AUTO_MAPPER_ANNOTATION = "io.github.linpl.annotations.AutoMapper"; + + public static final String MAPPER_CONFIG_ANNOTATION = "io.github.linpl.annotations.MapperConfig"; + + public static final String BASE_MAPPER_PACKAGE = "io.github.linpl"; + + public static final String BASE_MAPPER_CLASS_NAME = "BaseMapper"; + + public static final String MAPSTRUCT_MAPPER_PACKAGE = "org.mapstruct"; + + public static final String MAPSTRUCT_MAPPER_CLASS_NAME = "Mapper"; + + +} diff --git a/mapstruct-plus-processor/src/main/java/io/github/linpl/processor/MapperConfigGenerator.java b/mapstruct-plus-processor/src/main/java/io/github/linpl/processor/MapperConfigGenerator.java new file mode 100644 index 0000000..ab8215c --- /dev/null +++ b/mapstruct-plus-processor/src/main/java/io/github/linpl/processor/MapperConfigGenerator.java @@ -0,0 +1,50 @@ +package io.github.linpl.processor; + +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.linpl.annotations.AutoMapper; +import java.io.IOException; +import java.io.Writer; +import javax.annotation.processing.ProcessingEnvironment; +import javax.annotation.processing.RoundEnvironment; +import javax.lang.model.element.Modifier; + +import static javax.tools.Diagnostic.Kind.ERROR; + +public class MapperConfigGenerator { + + public void write(ProcessingEnvironment processingEnv) { + try (final Writer writer = processingEnv.getFiler() + .createSourceFile(AutoMapperProperties.getMapperPackage() + "." + AutoMapperProperties.getConfigClassName()) + .openWriter()) { + JavaFile.builder(AutoMapperProperties.getMapperPackage(), createConfigTypeSpec()).build().writeTo(writer); + } catch (IOException e) { + processingEnv.getMessager() + .printMessage(ERROR, + "Error while opening " + AutoMapperProperties.getConfigClassName() + " output file: " + + e.getMessage()); + } + } + + private TypeSpec createConfigTypeSpec() { + return TypeSpec.interfaceBuilder(AutoMapperProperties.getConfigClassName()) + .addModifiers(Modifier.PUBLIC) + .addAnnotation(buildMapperConfigAnnotationSpec()) + .build(); + } + + private AnnotationSpec buildMapperConfigAnnotationSpec() { + CodeBlock.Builder usesCodeBuilder = CodeBlock.builder().add("{"); + usesCodeBuilder.add("$T.class", + ClassName.get(AutoMapperProperties.getAdapterPackage(), AutoMapperProperties.getAdapterClassName())); + CodeBlock usesCodeBlock = usesCodeBuilder.add("}").build(); + return AnnotationSpec.builder(ClassName.get("org.mapstruct", "MapperConfig")) + .addMember("componentModel", "\"spring\"") + .addMember("uses", usesCodeBlock) + .build(); + } + +} diff --git a/mapstruct-plus-processor/src/main/resources/META-INF/services/javax.annotation.processing.Processor b/mapstruct-plus-processor/src/main/resources/META-INF/services/javax.annotation.processing.Processor new file mode 100644 index 0000000..7c72e80 --- /dev/null +++ b/mapstruct-plus-processor/src/main/resources/META-INF/services/javax.annotation.processing.Processor @@ -0,0 +1 @@ +io.github.linpl.processor.AutoMapperProcessor \ No newline at end of file diff --git a/mapstruct-plus-spring-boot-starter/pom.xml b/mapstruct-plus-spring-boot-starter/pom.xml new file mode 100644 index 0000000..f029b2b --- /dev/null +++ b/mapstruct-plus-spring-boot-starter/pom.xml @@ -0,0 +1,49 @@ + + + + mapstruct-plus + io.github.linpl + ${mapstruct-plus.version} + ../pom.xml + + 4.0.0 + + mapstruct-plus-spring-boot-starter + + + 8 + 8 + UTF-8 + 2.4.5 + + + + + + org.springframework.boot + spring-boot-dependencies + ${spring-boot.version} + pom + import + + + + + + + io.github.linpl + mapstruct-plus-core + + + org.springframework.boot + spring-boot-configuration-processor + + + org.springframework.boot + spring-boot-autoconfigure + + + + \ No newline at end of file diff --git a/mapstruct-plus-spring-boot-starter/src/main/java/io/github/linpl/mapstruct/MapstructAutoConfiguration.java b/mapstruct-plus-spring-boot-starter/src/main/java/io/github/linpl/mapstruct/MapstructAutoConfiguration.java new file mode 100644 index 0000000..efef38d --- /dev/null +++ b/mapstruct-plus-spring-boot-starter/src/main/java/io/github/linpl/mapstruct/MapstructAutoConfiguration.java @@ -0,0 +1,22 @@ +package io.github.linpl.mapstruct; + +import io.github.linpl.Converter; +import io.github.linpl.ConverterFactory; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class MapstructAutoConfiguration { + + @Bean + public ConverterFactory converterFactory(ApplicationContext applicationContext) { + return new SpringConverterFactory(applicationContext); + } + + @Bean + public Converter converter(ConverterFactory converterFactory) { + return new Converter(converterFactory); + } + +} diff --git a/mapstruct-plus-spring-boot-starter/src/main/java/io/github/linpl/mapstruct/SpringConverterFactory.java b/mapstruct-plus-spring-boot-starter/src/main/java/io/github/linpl/mapstruct/SpringConverterFactory.java new file mode 100644 index 0000000..0b5725e --- /dev/null +++ b/mapstruct-plus-spring-boot-starter/src/main/java/io/github/linpl/mapstruct/SpringConverterFactory.java @@ -0,0 +1,26 @@ +package io.github.linpl.mapstruct; + +import io.github.linpl.BaseMapper; +import io.github.linpl.ConverterFactory; +import org.springframework.context.ApplicationContext; +import org.springframework.core.ResolvableType; + +public class SpringConverterFactory implements ConverterFactory { + + private final ApplicationContext applicationContext; + + public SpringConverterFactory(final ApplicationContext applicationContext) { + this.applicationContext = applicationContext; + } + + @Override + public BaseMapper getMapper(final Class sourceType, final Class targetType) { + ResolvableType type = ResolvableType.forClassWithGenerics( + BaseMapper.class, sourceType, targetType); + String[] beanNames = applicationContext.getBeanNamesForType(type); + if (beanNames.length == 0) { + return null; + } + return (BaseMapper) applicationContext.getBean(beanNames[0]); + } +} diff --git a/mapstruct-plus-spring-boot-starter/src/main/resources/META-INF/spring.factories b/mapstruct-plus-spring-boot-starter/src/main/resources/META-INF/spring.factories new file mode 100644 index 0000000..e541abf --- /dev/null +++ b/mapstruct-plus-spring-boot-starter/src/main/resources/META-INF/spring.factories @@ -0,0 +1 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=io.github.linpl.mapstruct.MapstructAutoConfiguration \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..552b5eb --- /dev/null +++ b/pom.xml @@ -0,0 +1,98 @@ + + + 4.0.0 + + io.github.linpl + mapstruct-plus + pom + ${mapstruct-plus.version} + + mapstruct-plus-core + mapstruct-plus-spring-boot-starter + mapstruct-plus-processor + + + + 1.0.0 + 8 + 8 + UTF-8 + + + + + + io.github.linpl + mapstruct-plus-core + ${mapstruct-plus.version} + + + io.github.linpl + mapstruct-plus-spring-boot-starter + ${mapstruct-plus.version} + + + com.baidu.lbsyun + javapoet + 1.9.0 + + + org.apache.commons + commons-lang3 + 3.12.0 + + + org.mapstruct + mapstruct + 1.5.1.Final + + + org.springframework + spring-core + 5.3.23 + + + + + + + + + org.codehaus.mojo + flatten-maven-plugin + 1.1.0 + + true + resolveCiFriendliesOnly + + + + flatten + process-resources + + flatten + + + + flatten.clean + clean + + clean + + + + + + + + + org.codehaus.mojo + flatten-maven-plugin + 1.1.0 + + + + + \ No newline at end of file