Skip to content

Commit

Permalink
new
Browse files Browse the repository at this point in the history
  • Loading branch information
Luozhan authored and luozhan committed Apr 27, 2020
1 parent 6e8df65 commit 3a274f9
Show file tree
Hide file tree
Showing 22 changed files with 1,191 additions and 2 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
# TranslateUtil
批量字典翻译,字典数据源支持静态字典表(缓存)、缓存、数据库实时翻译
# 翻译工具Translator
批量数据字典翻译,字典数据源支持静态字典表
48 changes: 48 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.6.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.robot</groupId>
<artifactId>translator</artifactId>
<version>1.0.0-SNAPSHOT</version>
<name>translator</name>
<description>Demo project for Spring Boot</description>

<properties>
<java.version>1.8</java.version>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.11</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>


</project>
15 changes: 15 additions & 0 deletions src/main/java/com/robot/translator/core/Application.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.robot.translator.core;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
* @author luozhan
* @create 2020-04
*/
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class);
}
}
282 changes: 282 additions & 0 deletions src/main/java/com/robot/translator/core/Translator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,282 @@
package com.robot.translator.core;

import com.robot.translator.core.annotation.Dictionary;
import com.robot.translator.core.annotation.Translate;
import com.robot.translator.core.dict.IDict;
import com.robot.translator.core.enums.FormatType;
import com.robot.translator.core.translator.DefaultTranslator;
import com.robot.translator.core.translator.EnumTranslator;
import com.robot.translator.core.translator.Translatable;
import com.robot.translator.core.util.SpringContextUtil;
import com.robot.translator.core.util.StringUtil;
import org.springframework.stereotype.Component;
import sun.reflect.annotation.AnnotationType;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static com.robot.translator.core.util.LambdaUtil.uncheck;

/**
* 翻译工具
* 结合注解使用
*
* @author luozhan
* @see Translate
* @see Dictionary
*/
@Component
public class Translator {

/**
* 翻译Map或Entity或Page
*
* @param origin 需要翻译的数据
* @param clazz 指定翻译的模板class,可传多个
* @param <T> Map或Entity
* @return origin
*/
public static <T> T parse(T origin, Class<?>... clazz) {
if (origin == null) {
return null;
}
return parse(Collections.singletonList(origin), clazz).get(0);
}

/**
* 翻译集合
* 集合元素为Map或Entity
* <p>
* 注意:
* 如果不指定class,默认使用List中元素的class上配置的翻译规则进行翻译
* 当List集合元素不为Entity类型时,class参数至少需要指定1个
*
* @param origins 待翻译数据集合
* @param classes 配置了翻译规则的Entity类型,可传多个
* @param <T> 支持Entity或者Map
* @return List
*/
public static <T> List<T> parse(List<T> origins, Class<?>... classes) {
if (origins.size() == 0) {
return origins;
}
classes = (classes.length != 0) ? classes : new Class[]{origins.get(0).getClass()};

// 获取bo中需要翻译的属性
List<Field> translateFieldList = Arrays.stream(classes)
.map(Class::getDeclaredFields)
.flatMap(Stream::of)
.filter(field -> field.isAnnotationPresent(Translate.class))
.collect(Collectors.toList());
// 源数据中属性的格式(大写下划线,小写下划线,驼峰)
FormatType fieldFormatType = getFieldType(origins);

for (Field field : translateFieldList) {
// 1.获取要翻译的属性名
String fieldName = StringUtil.parseCamelTo(field.getName(), fieldFormatType);

// 2.获取每个待翻译属性的配置
Translate translateConfig = field.getAnnotation(Translate.class);
// 配置的字典class
Class<?> dictClass = getDictClass(translateConfig);
if (dictClass == void.class) {
throw new IllegalArgumentException(String.format("属性名%s上的@Translate注解找不到dictClass或value配置", fieldName));
}

// 获取翻译值写入的字段名
String translateField = StringUtil.parseCamelTo(getTranslateFieldName(translateConfig, field.getName()), fieldFormatType);

// 字典组字段值
String groupValue = translateConfig.groupValue();
// 字典注解配置
Dictionary dictionaryConfig = handle(translateConfig);

for (T origin : origins) {
Object originValue = getProperty(origin, fieldName);
if (originValue == null) {
continue;
}
// 翻译
String translateValue = parse(String.valueOf(originValue), dictionaryConfig, dictClass, groupValue);
// 填值
setProperty(origin, translateField, translateValue);
}
}
return origins;
}

/**
* 判断源数据中的属性格式类型
*/
@SuppressWarnings("unchecked")
private static <T> FormatType getFieldType(List<T> origins) {
T element = origins.get(0);
if (Map.class.isAssignableFrom(element.getClass())) {
Set<String> keySet = ((Map) element).keySet();
for (String key : keySet) {
if (key.toUpperCase().equals(key)) {
return FormatType.UPPERCASE_UNDERLINE;
} else if (key.contains("_")) {
return FormatType.LOWERCASE_UNDERLINE;
}
}
}
return FormatType.CAMEL;

}

private static Class<?> getDictClass(Translate translateConfig) {
return translateConfig.dictClass() == void.class ? translateConfig.value() : translateConfig.dictClass();
}

/**
* 将dictClass类上的Dictionary的配置填充到Translate注解中的dictionary属性中
* 此步骤将合并两个注解中的配置,且Translate注解中的配置优先级更高
*/
private static Dictionary handle(Translate translateConfig) {
Class<?> dictClass = getDictClass(translateConfig);
Dictionary dictionaryConfigOnDictClass = dictClass.getAnnotation(Dictionary.class);
Dictionary dictionaryConfigInTranslateConfig = translateConfig.dictionary();
return (Dictionary) joinAnnotationValue(dictionaryConfigOnDictClass, dictionaryConfigInTranslateConfig);
}

/**
* 将注解属性填充到另一个相同类型的注解中,目标注解中已经存在属性值的不会被覆盖
*
* @param annotationFrom
* @param annotationTo
* @return 返回annotationTo,如果annotationTo为空,返回annotationFrom
*/
@SuppressWarnings("all")
private static Annotation joinAnnotationValue(Annotation annotationFrom, Annotation annotationTo) {
if (annotationTo == null) {
return annotationFrom;
}
if (annotationFrom == null) {
return annotationTo;
}
Object handlerFrom = Proxy.getInvocationHandler(annotationFrom);
Object handlerTo = Proxy.getInvocationHandler(annotationTo);

Field fieldFrom = uncheck(() -> handlerFrom.getClass().getDeclaredField("memberValues"));
Field fieldTo = uncheck(() -> handlerTo.getClass().getDeclaredField("memberValues"));

fieldFrom.setAccessible(true);
fieldTo.setAccessible(true);

Map<String, Object> memberValuesFrom = uncheck(() -> (Map) fieldFrom.get(handlerFrom));
Map<String, Object> memberValuesTo = uncheck(() -> (Map) fieldTo.get(handlerTo));

// 注解默认值,注意不会包含没有默认值的属性
Map<String, Object> defaultValueMap = AnnotationType.getInstance(annotationTo.annotationType()).memberDefaults();
// 若目标注解中全都是默认值(代表没有设置),则直接返回原注解
// 否则属性填充后会直接改变目标注解的默认值,影响其他引用的地方
if (!defaultValueMap.equals(memberValuesTo)) {
return annotationFrom;
}
// 如果目标注解属性未设置,则往目标里填充值
memberValuesTo.forEach((field, value) -> {
if (value.equals(defaultValueMap.get(field))) {
memberValuesTo.put(field, memberValuesFrom.get(field));
}
});

return annotationTo;
}


/**
* 翻译单值
*
* @param originValue 原始值
* @param dictConfig 字典配置
* @param dictClass 字典class(包含组别属性、字典code属性、字典值属性三个信息)
* @param groupValue 组别的值,由使用者指定
* @return 翻译后的值,如果字典中找不到翻译值返回原始值
*/
@SuppressWarnings("all")
public static String parse(String originValue, Dictionary dictConfig, Class<?> dictClass, String groupValue) {
if (originValue == null) {
return null;
}
Class<? extends Translatable> translatorClass = dictConfig.translator();
if (translatorClass == Translatable.class) {
// 未指定translator,采用默认配置:
// todo:默认翻译类支持配置化
if (IDict.class.isAssignableFrom(dictClass)) {
// 1.dictClass是枚举类,采用枚举翻译
translatorClass = EnumTranslator.class;
} else {
// 2.否则使用默认翻译
translatorClass = DefaultTranslator.class;
}
}

// 调用翻译方法
Translatable translator;
if (translatorClass.isAnnotationPresent(Component.class)) {
// 实现类上配置了@Component则使用Spring容器获取
translator = SpringContextUtil.getBean(translatorClass);
} else {
translator = uncheck(translatorClass::newInstance);
}
String translateResult = translator.translate(groupValue, originValue, dictConfig, dictClass);
return translateResult == null ? originValue : translateResult;
}

/**
* 若注解中未配置translateField,则默认将原属性名的Id或Code字样替换成Name
* <p>
* 如:
* resTypeId -> resTypeCode
* staff -> staffName
*/
private static String getTranslateFieldName(Translate translateConfig, String originFieldName) {
String translateField = translateConfig.translateField();
if ("".equals(translateField)) {
translateField = originFieldName.replaceFirst("(Id|Code)$|$", "Name");
}
return translateField;
}


private static Object getProperty(Object o, String fieldName) {
if (o instanceof Map) {
return ((Map) o).get(fieldName);
} else {
Method getMethod = getMethod(o.getClass(), fieldName, "get");
// 此处不会抛异常
return uncheck(() -> getMethod.invoke(o));
}
}

@SuppressWarnings("unchecked")
private static void setProperty(Object o, String fieldName, String value) {
if (o instanceof Map) {
((Map) o).put(fieldName, value);
} else {
Method setMethod = getMethod(o.getClass(), fieldName, "set");
// 此处不会抛异常
uncheck(() -> setMethod.invoke(o, value));
}
}

/**
* 通过方法名找到get或set方法
* 只是简单根据名称匹配,要求clazz是个常规的bean
*/
private static Method getMethod(Class<?> clazz, String fieldName, String prefix) {
String methodName = prefix + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
return Stream.of(clazz.getDeclaredMethods())
.filter(method -> method.getName().equals(methodName))
.findAny()
.orElseThrow(() -> new IllegalArgumentException(clazz.getSimpleName() + ".class中未添加翻译属性" + fieldName + "或其对应get/set方法"));
}

}

Loading

0 comments on commit 3a274f9

Please sign in to comment.