docs: Translate the Chinese comments in the event module into English.

This commit is contained in:
jaysunxiao committed 2026-05-07 17:04:30 +08:00
1 parent d9fd135208
commit 1d0b2886a8
36 files changed
+180 -181

No files matched your search

@@ -69,18 +69,18 @@ public class StorageContext implements ApplicationListener<ApplicationContextEve
if (event instanceof ContextRefreshedEvent) {
var stopWatch = new StopWatch();
// 初始化上下文
// Initialize context
StorageContext.instance = this;
instance.applicationContext = event.getApplicationContext();
instance.storageManager = applicationContext.getBean(IStorageManager.class);
// 初始化,并读取配置表
// Initialize and load resource tables
instance.storageManager.initBefore();
// 注入配置表资源
// Inject resource table data
instance.storageManager.inject();
// 移除没有被引用的不必要资源,为了节省服务器内存
// Remove unreferenced resources to save server memory
instance.storageManager.initAfter();
logger.info("Storage started successfully and cost [{}] seconds", stopWatch.costSeconds());
} else if (event instanceof ContextClosedEvent) {
@@ -18,7 +18,7 @@ import java.lang.annotation.*;
/**
* EN: Specify the file column name. If not specified, the default column name will be consistent with the field name.
* CN: 指定文件列名,不指定则默认列名与字段名一致
* Specifies the Excel column name; defaults to the field name if not specified
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@@ -18,7 +18,7 @@ import java.lang.annotation.*;
/**
* EN: the name of the index uses the name of the field attribute, implemented with HaspMap
* CN: 索引的名称使用字段属性的名称,用HaspMap实现
* Index name uses the field name; implemented with HashMap
*
* @author godotg
*/
@@ -19,7 +19,7 @@ import java.lang.annotation.*;
/**
*
* EN: You can specify the corresponding resource file name (only the file name is specified, no file suffix is required)
* CN: 可以指定对应的资源文件名(只指定文件名,不需要文件后缀),如果不指定资源文件名,则默认通过扫描路径获取与类名相同的文件资源
* Specifies the resource file name (name only, no extension); defaults to scanning for a file matching the class name
*
* @author godotg
*/
@@ -18,7 +18,7 @@ import java.lang.annotation.*;
/**
* EN: Injection of static data
* CN: 静态数据的注入
* Injection of static data resources
*
* @author godotg
*/
@@ -23,10 +23,10 @@ public class StorageConfig {
private String resourceLocation;
// 类的属性是否可写,如果为false则类的属性必须为private并且不能有set方法
// Whether class fields are writable; if false, fields must be private with no setters
private boolean writeable;
// 未被使用的Storage是否回收,默认开启节省资源
// Whether unused Storage instances are recycled; default is true to save memory
private boolean recycle;
public String getId() {
@@ -45,11 +45,11 @@ public class ArrayConverter implements ConditionalGenericConverter {
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
var content = StringUtils.trim((String) source);
var componentType = targetType.getType().getComponentType();
// null safe,content为空则返回长度为0的数组
// Null-safe: returns empty array if content is null
if (StringUtils.isEmpty(content)) {
return Array.newInstance(componentType, 0);
}
// 如果为json格式,则以json格式解析
// If JSON format, parse as JSON
if (content.startsWith("[") && content.endsWith("]")) {
try {
return componentType.isPrimitive()
@@ -23,7 +23,7 @@ import java.util.Collections;
import java.util.Set;
/**
* 转换一个String到一个POJO对象,且这个对象不能继承如何接口
* Converts a String to a POJO object; the object must not implement any interfaces
*
* @author godotg
*/
@@ -54,20 +54,20 @@ public abstract class CsvReader {
/**
* 构建配置表消息头
* Build resource table header
*/
private static List<StorageHeader> getHeaders(Iterator<CSVRecord> iterator, String fileName) {
// 获取配置表的有效列名称,默认第一行就是字段名称
// Get valid column names from the resource table; row 1 is the field names by default
var fieldRow = iterator.next();
if (fieldRow == null) {
throw new RunException("Failed to get attribute control column from csv file of resource [class:{}]", fileName);
}
//默认第二行字段类型
// Row 2: field types (default)
var typeRow = iterator.next();
if (typeRow == null) {
throw new RunException("Failed to get type control column from csv file of resource [class:{}]", fileName);
}
// 默认第三行为描述,需要的时候再使用
// Row 3: descriptions (optional)
var descRow = iterator.next();
var headers = new ArrayList<StorageHeader>();
@@ -21,7 +21,6 @@ import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.usermodel.WorkbookFactory;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;
@@ -31,12 +30,12 @@ import java.util.*;
public abstract class ExcelReader {
public static StorageData readResourceDataFromExcel(InputStream inputStream, String resourceClassName) {
// 只读取代码里写的字段
// Only read fields declared in the code
var wb = createWorkbook(inputStream, resourceClassName);
// 默认取到第一个sheet页
// Read the first worksheet by default
var sheet = wb.getSheetAt(0);
var iterator = sheet.iterator();
// 设置所有列
// Set all columns
var excelHeaders = getHeaders(iterator, resourceClassName);
var rows = new ArrayList<List<String>>();
@@ -57,7 +56,7 @@ public abstract class ExcelReader {
rows.add(columns);
}
// excel某些格子空的引起的列偏移
// Handle column offset caused by empty cells in Excel
var headers = new ArrayList<StorageHeader>();
for (var i = 0; i < excelHeaders.size(); i++) {
var excelHeader = excelHeaders.get(i);
@@ -67,17 +66,17 @@ public abstract class ExcelReader {
}
private static List<StorageHeader> getHeaders(Iterator<Row> iterator, String resourceClassName) {
// 获取配置表的有效列名称,默认第一行就是字段名称
// Get valid column names from the resource table; row 1 is the field names by default
var fieldRow = iterator.next();
if (fieldRow == null) {
throw new RunException("Failed to get attribute control column from excel file of resource [class:{}]", resourceClassName);
}
// 默认第二行字段类型
// Row 2: field types (default)
var typeRow = iterator.next();
if (typeRow == null) {
throw new RunException("Failed to get type control column from excel file of resource [class:{}]", resourceClassName);
}
// 默认第三行为描述,需要的时候再使用
// Row 3: descriptions (optional)
var desRow = iterator.next();
var headerList = new ArrayList<StorageHeader>();
var cellFieldMap = new HashMap<String, Integer>();
@@ -47,7 +47,7 @@ public class ResourceInterpreter {
}
var result = new ArrayList<T>();
//获取所有字段
// Get all fields
var cellFieldMap = getCellFieldMap(resource, clazz);
var fieldInfos = getFieldInfos(cellFieldMap, clazz);
@@ -56,7 +56,7 @@ public class ResourceInterpreter {
Constructor<T> constructor = ReflectionUtils.getConstructor(clazz, constructParams);
var iterator = resource.getRows().iterator();
// 从ROW_SERVER这行开始读取数据
// Start reading data from ROW_SERVER
while (iterator.hasNext()) {
int index = 0;
var columns = iterator.next();
@@ -74,7 +74,7 @@ public class ResourceInterpreter {
}
} else {
var iterator = resource.getRows().iterator();
// 从ROW_SERVER这行开始读取数据
// Start reading data from ROW_SERVER
while (iterator.hasNext()) {
var columns = iterator.next();
var instance = ReflectionUtils.newInstance(clazz);
@@ -100,15 +100,15 @@ public class ResourceInterpreter {
}
}
// 优先使用ExcelFieldName注解表示的值当作列名
// @ExcelFieldName value takes priority as the column name
private static String getExcelFieldName(Field field) {
return field.isAnnotationPresent(AliasFieldName.class) ? field.getAnnotation(AliasFieldName.class).value() : field.getName();
}
// 只读取代码里写的字段
// Only read fields declared in the code
private static Collection<FieldInfo> getFieldInfos(Map<String, Integer> cellFieldMap, Class<?> clazz) {
var fieldList = ReflectionUtils.notStaticAndTransientFields(clazz);
// 检测field的合法性,field必须可以在excel中找到对应的列,有找不到的列在启动时候就发现
// Validate field: must have a matching column in Excel; missing columns are detected at startup
for (var field : fieldList) {
var fieldName = getExcelFieldName(field);
if (!cellFieldMap.containsKey(fieldName)) {
@@ -15,17 +15,17 @@ import java.util.ArrayList;
import java.util.List;
/**
* 配置文件资源
* Resource configuration data
*
* @author meiwei666
*/
public class StorageData {
// 文件名
// File name
private String name;
// 配置表字段名
// Resource table field names
private List<StorageHeader> headers = new ArrayList<>();
// 配置表数据
// Resource table data
private List<List<String>> rows = new ArrayList<>();
public static StorageData valueOf(String name, List<StorageHeader> headers, List<List<String>> rows) {
@@ -19,7 +19,7 @@ import java.util.HashMap;
import java.util.Map;
/**
* 支持读取配置文件的类型后缀
* Supported file type suffixes for resource files
*
* @author godotg
*/
@@ -16,11 +16,11 @@ package com.zfoo.storage.interpreter.data;
*/
public class StorageHeader {
//字段名
// Field name
private String name;
//类型
// Field type
private String type;
//列
// Column
private int index;
public static StorageHeader valueOf(String name, String type, int index) {
@@ -38,16 +38,16 @@ import java.util.*;
public abstract class AbstractStorage<K, V> implements IStorage<K, V> {
// func index of caches
private ConcurrentReferenceHashMap<Func1<V, ?>, String> funcCaches = new ConcurrentReferenceHashMap<>();
// non-unique index (非唯一索引)
// non-unique index
protected Map<String, Map<Object, List<V>>> indexMap = new HashMap<>();
// unique index (唯一索引)
// unique index
protected Map<String, Map<Object, V>> uniqueIndexMap = new HashMap<>();
protected Class<?> clazz;
protected IdDef idDef;
protected Map<String, IndexDef> indexDefMap;
// EN: unused configuration tables will clear data to save memory.
// CN: 没有被使用的配置表会清除data数据,以达到节省内存的目的
// Unused resource tables have their data cleared to save memory
protected boolean recycle = true;
public static AbstractStorage<?, ?> parse(InputStream inputStream, Class<?> resourceClazz, String suffix) {
@@ -56,7 +56,7 @@ public abstract class AbstractStorage<K, V> implements IStorage<K, V> {
try {
var list = ResourceInterpreter.read(inputStream, resourceClazz, suffix);
// 校验id是否重复
// Check for duplicate ids
var set = new HashSet<>();
for (var value : list) {
var id = ReflectionUtils.getField(idDef.getField(), value);
@@ -89,12 +89,12 @@ public abstract class AbstractStorage<K, V> implements IStorage<K, V> {
this.idDef = idDef;
this.indexDefMap = indexDefMap;
for (var value : values) {
// 添加资源
// Add resource
@SuppressWarnings("unchecked")
var v = (V) value;
// 添加索引
// Add index
for (var def : indexDefMap.values()) {
// 使用field的名称作为索引的名称
// Use field name as the index name
var indexKey = def.getField().getName();
var indexValue = ReflectionUtils.getField(def.getField(), v);
if (def.isUnique()) {
@@ -165,7 +165,7 @@ public abstract class AbstractStorage<K, V> implements IStorage<K, V> {
return indexName;
}
// 1. IDEA 调试模式下 lambda 表达式是一个代理
// 1. In IDEA debug mode, lambda is a proxy
if (func instanceof Proxy) {
try {
var lambda = new IdeaProxyLambdaMeta((Proxy) func);
@@ -174,7 +174,7 @@ public abstract class AbstractStorage<K, V> implements IStorage<K, V> {
}
}
// 2. 反射读取
// 2. Read via reflection
if (indexName == null) {
try {
var method = func.getClass().getDeclaredMethod("writeReplace");
@@ -185,7 +185,7 @@ public abstract class AbstractStorage<K, V> implements IStorage<K, V> {
}
}
// 3. 反射失败使用序列化的方式读取
// 3. Fallback to serialization if reflection fails
if (indexName == null) {
try {
var lambda = new ShadowLambdaMeta(SerializedLambda.extract(func));
@@ -194,7 +194,7 @@ public abstract class AbstractStorage<K, V> implements IStorage<K, V> {
}
}
// 4. 通过将func带入到dataMap中求解,适合GraalVM环境中
// 4. Derive from dataMap (suitable for GraalVM environments)
if (indexName == null) {
try {
var fields = Arrays.stream(clazz.getDeclaredFields())
@@ -223,7 +223,7 @@ public abstract class AbstractStorage<K, V> implements IStorage<K, V> {
}
}
// 所有的方法都取不到Func对应的属性名称则抛出异常
// Throw exception if no method can determine the Func field name
if (indexName == null) {
throw new RunException("can not get indexName from func:[{}]", func);
}
@@ -24,17 +24,17 @@ import java.util.Map;
public interface IStorageManager {
/**
* 配置表初始化之前,先读取所有的excel
* Read all Excel files before initializing resource tables
*/
void initBefore();
/**
* 注入
* Inject
*/
void inject();
/**
* 程序加载过后,移除没有用到的配置表
* Remove unused resource tables after loading to free memory
*/
void initAfter();
@@ -54,19 +54,19 @@ public class StorageManager implements IStorageManager {
private static final Logger logger = LoggerFactory.getLogger(StorageContext.class);
// ANT通配符有三种, ? :匹配任何单字符; * :匹配0或者任意数量的字符; ** :匹配0或者更多的目录
// 1. /project/*.a 匹配项目根路径下所有在project路径下的.a文件
// 2. /project/p?ttern 匹配项目根路径下 /project/pattern 和 /app/pXttern,但是不包括/app/pttern
// 3. /**/example 匹配项目根路径下 /project/example, /project/foow/example, 和 /example
// 4. /project/**/dir/file.* 匹配项目根路径下/project/dir/file.jsp, /project/foow/dir/file.html,/project/foow/bar/dir/file.pdf
// 5. /**/*.jsp 匹配项目根路径下任何的.jsp 文件
// classpath:和classpath*: 的区别,前者只会第一个加载到的类,后者会加载所有的类,包括jar文件下的类
// ANT wildcards: '?' one char, '*' any chars, '**' any path
// 1. /project/*.a matches all .a files under /project
// 2. /project/p?ttern matches /project/pattern and /project/pXttern (not pttern)
// 3. /**/example matches any path ending with /example
// 4. /project/**/dir/file.* matches /project/**/dir/file.*
// 5. /**/*.jsp matches all .jsp files anywhere
// 'classpath:' loads first match; 'classpath*:' loads all including JARs
private static final String SUFFIX_PATTERN = "**/*.class";
private StorageConfig storageConfig;
/**
* 在当前项目被依赖注入,被使用的Storage
* Storage resources that are dependency-injected in the current project
*/
private final Map<Class<?>, IStorage<?, ?>> storageMap = new HashMap<>();
@@ -82,25 +82,25 @@ public class StorageManager implements IStorageManager {
public void initBefore() {
var resourceDefinitionMap = new HashMap<Class<?>, StorageDefinition>();
// 获取需要被映射的Excel的class类文件
// Get the class mapped to Excel
var clazzSet = resourceClass();
if (CollectionUtils.isEmpty(clazzSet)) {
logger.warn("no any storages found, please check your config. If is in graalvm environment, make sure your storage be annotated with @GraalvmNativeStorage and can be scanned by spring @Component.");
return;
}
// 通过class类文件扫描excel文件地址
// Scan for Excel file path based on class
for (var resourceClazz : clazzSet) {
var resourceFile = resource(resourceClazz);
StorageDefinition storageDefinition = new StorageDefinition(resourceClazz, resourceFile);
if (resourceDefinitionMap.containsKey(resourceClazz)) {
// 类的资源定义已经存在
// Resource definition already registered
throw new RuntimeException(StringUtils.format("The resource definition of the class [{}] already exists [{}]", resourceClazz, storageDefinition));
}
resourceDefinitionMap.put(resourceClazz, storageDefinition);
}
// 检查class字段是否合法
// Validate class fields
if (!storageConfig.isWriteable()) {
var allRelevantClass = new HashSet<Class<?>>();
resourceDefinitionMap.values().forEach(it -> allRelevantClass.addAll(ClassUtils.relevantClass(it.getClazz())));
@@ -113,7 +113,7 @@ public class StorageManager implements IStorageManager {
var fieldList = ReflectionUtils.notStaticAndTransientFields(clazz);
for (var field : fieldList) {
if (Modifier.isPublic(field.getModifiers())) {
// 因为静态资源类是不能被修改的,资源类的属性不能被public修饰,用private修饰或者开启配置writeable属性
// Static resource fields must be private; use writeable=true to allow setters
throw new RunException("Static resource classes cannot be modified, class:[{}] filed:{}[] cannot be modified by public, use private modified or enable writeable configuration", clazz, field.getName());
}
@@ -121,10 +121,10 @@ public class StorageManager implements IStorageManager {
try {
setMethodName = FieldUtils.fieldToSetMethod(clazz, field);
} catch (Exception e) {
// 没有setMethod是正确的
// No setter is correct for immutable resources
}
if (StringUtils.isNotBlank(setMethodName)) {
// 因为静态资源类是不能被修改的,资源类的属性不能含有set方法[{}],删除set方法或者开启配置writeable属性
// Static resource fields must not have setters; remove the setter or enable writeable
throw new RunException("Static resource classes cannot be modified, [class:{}][filed:{}] cannot contain set method [{}], delete set method or enable writeable configuration", clazz, field.getName(), setMethodName);
}
}
@@ -150,7 +150,7 @@ public class StorageManager implements IStorageManager {
var componentBeans = applicationContext.getBeansWithAnnotation(Component.class);
for (var bean : componentBeans.values()) {
//防止被CGLIB代理时 直接赋值无效
// Prevent direct assignment being ignored when CGLIB-proxied
var targetBean = Objects.requireNonNullElse(AopProxyUtils.getSingletonTarget(bean), bean);
var clazz = targetBean.getClass();
@@ -181,7 +181,7 @@ public class StorageManager implements IStorageManager {
}
if (!keyClazz.getSimpleName().toLowerCase().contains(idFields[0].getType().getSimpleName().toLowerCase())) {
// 注入静态类配置资源的类型和泛型类型不匹配
// Injected type does not match the expected generic type
throw new RuntimeException(StringUtils.format("Inject static class configuration storage:[{}] key:[{}] type and generic type type:[{}] do not match", resourceClazz.getSimpleName(), idFields[0].getType().getSimpleName(), keyClazz.getSimpleName()));
}
@@ -223,7 +223,7 @@ public class StorageManager implements IStorageManager {
throw new RunException("There is no [{}] defined Storage and unable to get it", clazz.getCanonicalName());
}
if (storage.isRecycle()) {
// Storage没有使用,为了节省内存提前释放了它;只有使用@StorageAutowired注解的Storage才能被动态获取或者关闭配置recycle属性
// Storage not in use; released to save memory. Use @StorageAutowired or recycle=false to keep it
logger.warn("Storage [{}] is not used, it was freed to save memory; use @StorageAutowired or turn off recycle configuration", clazz.getCanonicalName());
}
return (T) storage;
@@ -245,7 +245,7 @@ public class StorageManager implements IStorageManager {
}
private Set<Class<?>> resourceClass() {
// graalvm环境 PathMatchingResourcePatternResolver/CachingMetadataReaderFactory 无法使用,所以直接在spring容器中获取
// In GraalVM: PathMatchingResourcePatternResolver unavailable; get resources from Spring context
if (GraalVmUtils.isGraalVM()) {
var applicationContext = StorageContext.getApplicationContext();
var nativeResources = applicationContext.getBeansWithAnnotation(GraalvmNativeStorage.class);
@@ -301,7 +301,7 @@ public class StorageManager implements IStorageManager {
var fileName = StringUtils.isEmpty(alias) ? clazz.getSimpleName() : alias;
try {
// 一个class类只能匹配一个资源文件,如果匹配多个则会有歧义
// A class can match only one resource file; multiple matches are ambiguous
var resourceSet = new HashSet<Resource>();
var resourceLocations = StringUtils.tokenize(storageConfig.getResourceLocation(), ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
for (var resourceLocation : resourceLocations) {
@@ -314,7 +314,7 @@ public class StorageManager implements IStorageManager {
// do nothing
}
// 通配符无法匹配根目录,所以如果找不到,再从根目录查找一遍
// Wildcards cannot match root directory; retry search from root
if (resources.isEmpty()) {
packageSearchPath = StringUtils.format("{}/{}.*", resourceLocation, fileName);
packageSearchPath = packageSearchPath.replaceAll("//", "/");
@@ -24,7 +24,7 @@ import java.util.HashMap;
import java.util.Map;
/**
* 简化索引的名称,使用字段的名称作为索引的名称
* Simplified index name using the field name
*
* @author godotg
*/
@@ -29,19 +29,19 @@ import org.w3c.dom.Element;
*/
public class StorageDefinitionParser implements BeanDefinitionParser {
// 配置标签名称
// Configuration tag name
public static final String STORAGE = "storage";
@Override
public AbstractBeanDefinition parse(Element element, ParserContext parserContext) {
// 解析StorageConfig的配置
// Parse StorageConfig
parseStorageConfig(element, parserContext);
// 注册StorageContext
// Register StorageContext
registerBeanDefinition(parserContext);
// 注册StorageManager
// Register StorageManager
var clazz = StorageManager.class;
var name = StringUtils.uncapitalize(clazz.getName());
var builder = BeanDefinitionBuilder.rootBeanDefinition(clazz);
@@ -80,7 +80,7 @@ public class StorageDefinitionParser implements BeanDefinitionParser {
String name;
BeanDefinitionBuilder builder;
// 注册StorageContext
// Register StorageContext
clazz = StorageContext.class;
name = StringUtils.uncapitalize(clazz.getName());
builder = BeanDefinitionBuilder.rootBeanDefinition(clazz);
@@ -32,9 +32,9 @@ public abstract class CellUtils {
}
/**
* 获取单元格值
* Get cell value
*
* @return 值,类型可能为:Date、Double、Boolean、String
* @return value; possible types: Date, Double, Boolean, String
*/
public static Object getCellValue(Cell cell) {
if (cell == null) {
@@ -44,8 +44,8 @@ public abstract class CellUtils {
}
/**
* 获取单元格值<br>
* 如果单元格值为数字格式,则判断其格式中是否有小数部分,无则返回Long类型,否则返回Double类型
* Get cell value<br>
* For numeric cells: return Long if no fractional part, otherwise Double
*/
private static Object getCellValue(Cell cell, CellType cellType) {
Object value;
@@ -57,7 +57,7 @@ public abstract class CellUtils {
value = cell.getBooleanCellValue();
break;
case FORMULA:
// 遇到公式时查找公式结果类型
// For formula cells, check the result type
value = getCellValue(cell, cell.getCachedFormulaResultType());
break;
case BLANK:
@@ -78,9 +78,9 @@ public abstract class CellUtils {
// -------------------------------------------------------------------------------------------------------------- Private method start
/**
* 获取数字类型的单元格值
* Get numeric cell value
*
* @return 单元格值,可能为Long、Double、Date
* @return cell value; type may be Long, Double, or Date
*/
private static Object getNumericValue(Cell cell) {
var value = cell.getNumericCellValue();
@@ -90,13 +90,13 @@ public abstract class CellUtils {
return value;
}
// 判断是否为日期
// Check if value is a date
if (DateUtil.isCellDateFormatted(cell)) {
return cell.getDateCellValue();
}
var format = style.getDataFormatString();
// 普通数字
// Plain number
if (null != format && !format.contains(StringUtils.PERIOD)) {
var longValue = (long) value;
if (longValue == value) {
@@ -92,7 +92,7 @@ public abstract class ExportUtils {
.toList();
}
// 将class里的map自动赋值storage
// Auto-assign class map to storage
public static <T> T autoWrapData(Class<T> clazz, Map<Class<?>, IStorage<?, ?>> storageMap) {
var instance = ReflectionUtils.newInstance(clazz);
@@ -148,25 +148,25 @@ public abstract class ExportUtils {
for (char c : valueChars) {
if (c == textDelimiter) {
// 字段值中存在包装符
// Field value contains a quote character
containsTextDelimiter = needsTextDelimiter = true;
break;
} else if (c == fieldSeparator || c == '\n' || c == '\r') {
// 包含分隔符或换行符需要包装符包装
// Wrap with quotes if value contains delimiter or newline
needsTextDelimiter = true;
}
}
// 包装符开始
// Opening quote
var builder = new StringBuilder();
if (needsTextDelimiter) {
builder.append(textDelimiter);
}
// 正文
// Content
if (containsTextDelimiter) {
for (char c : valueChars) {
// 转义文本包装符
// Escape embedded quote
if (c == textDelimiter) {
builder.append(textDelimiter);
}
@@ -176,7 +176,7 @@ public abstract class ExportUtils {
builder.append(valueChars);
}
// 包装符结尾
// Closing quote
if (needsTextDelimiter) {
builder.append(textDelimiter);
}
@@ -3,25 +3,25 @@ package com.zfoo.storage.util.function;
import java.io.Serializable;
/**
* 函数对象<br>
* 接口灵感来自于<a href="http://actframework.org/">ActFramework</a><br>
* 一个函数接口代表一个一个函数,用于包装一个函数为对象<br>
* 在JDK8之前,Java的函数并不能作为参数传递,也不能作为返回值存在,此接口用于将一个函数包装成为一个对象,从而传递对象
* Function object<br>
* Inspired by ActFramework<a href="http://actframework.org/">ActFramework</a><br>
* A functional interface wrapping a function as an object<br>
* Before JDK 8, functions could not be passed/returned; this wraps a function as an object
*
* @author Looly
*
* @param <P> 参数类型
* @param <R> 返回值类型
* @param <P> parameter type
* @param <R> return type
* @since 3.1.0
*/
@FunctionalInterface
public interface Func<P, R> extends Serializable {
/**
* 执行函数
* Execute the function
*
* @param parameters 参数列表
* @return 函数执行结果
* @throws Exception 自定义异常
* @param parameters parameter list
* @return function result
* @throws Exception custom exception
*/
@SuppressWarnings("unchecked")
R apply(P... parameters) throws Exception;
@@ -3,23 +3,23 @@ package com.zfoo.storage.util.function;
import java.io.Serializable;
/**
* 无参数的函数对象<br>
* 接口灵感来自于<a href="http://actframework.org/">ActFramework</a><br>
* 一个函数接口代表一个一个函数,用于包装一个函数为对象<br>
* 在JDK8之前,Java的函数并不能作为参数传递,也不能作为返回值存在,此接口用于将一个函数包装成为一个对象,从而传递对象
* No-parameter Function object<br>
* Inspired by ActFramework<a href="http://actframework.org/">ActFramework</a><br>
* A functional interface wrapping a function as an object<br>
* Before JDK 8, functions could not be passed/returned; this wraps a function as an object
*
* @author Looly
*
* @param <R> 返回值类型
* @param <R> return type
* @since 4.5.2
*/
@FunctionalInterface
public interface Func0<R> extends Serializable {
/**
* 执行函数
* Execute the function
*
* @return 函数执行结果
* @throws Exception 自定义异常
* @return function result
* @throws Exception custom exception
*/
R apply() throws Exception;
@@ -3,26 +3,26 @@ package com.zfoo.storage.util.function;
import java.io.Serializable;
/**
* 只有一个参数的函数对象<br>
* 接口灵感来自于<a href="http://actframework.org/">ActFramework</a><br>
* 一个函数接口代表一个一个函数,用于包装一个函数为对象<br>
* 在JDK8之前,Java的函数并不能作为参数传递,也不能作为返回值存在,此接口用于将一个函数包装成为一个对象,从而传递对象
* Single-parameter Function object<br>
* Inspired by ActFramework<a href="http://actframework.org/">ActFramework</a><br>
* A functional interface wrapping a function as an object<br>
* Before JDK 8, functions could not be passed/returned; this wraps a function as an object
*
* @author Looly
*
* @param <P> 参数类型
* @param <R> 返回值类型
* @param <P> parameter type
* @param <R> return type
* @since 4.2.2
*/
@FunctionalInterface
public interface Func1<P, R> extends Serializable {
/**
* 执行函数
* Execute the function
*
* @param parameter 参数
* @return 函数执行结果
* @throws Exception 自定义异常
* @param parameter the parameter
* @return function result
* @throws Exception custom exception
*/
R apply(P parameter) throws Exception;
@@ -7,7 +7,7 @@ import java.lang.reflect.Executable;
import java.lang.reflect.Proxy;
/**
* 在 IDEA 的 Evaluate 中执行的 Lambda 表达式元数据需要使用该类处理元数据
* Handles Lambda metadata for expressions evaluated in IDEA's Evaluate dialog
* <p>
* Create by hcl at 2021/5/17
*/
@@ -1,16 +1,16 @@
package com.zfoo.storage.util.lambda;
/**
* Lambda 信息
* Lambda metadata
* <p>
* Created by hcl at 2021/5/14
*/
public interface LambdaMeta {
/**
* 获取 lambda 表达式实现方法的名称
* Get the implementation method name for the lambda
*
* @return lambda 表达式对应的实现方法名称
* @return the implementation method name
*/
String getImplMethodName();
@@ -3,7 +3,7 @@ package com.zfoo.storage.util.lambda;
import java.io.*;
/**
* 当前类是 {@link java.lang.invoke.SerializedLambda } 的一个镜像
* A mirror of {@link java.lang.invoke.SerializedLambda}
* <p>
* Create by hcl at 2020/7/17
*/
@@ -1,7 +1,7 @@
package com.zfoo.storage.util.lambda;
/**
* 基于 {@link SerializedLambda} 创建的元信息
* Metadata based on {@link SerializedLambda}
* <p>
* Create by hcl at 2021/7/7
*/
@@ -38,54 +38,54 @@ public class ApplicationTest {
private static final Logger logger = LoggerFactory.getLogger(ApplicationTest.class);
// storage教程
// Storage tutorial
@Test
public void startStorageTest() {
// 加载配置文件,配置文件中必须引入storage
// 配置文件中scan,需要映射Excel的类所在位置,会自动搜索文件夹下的Excel文件,Excel文件可以放在指定文件夹的任意目录
// 配置文件中resource,需要映射Excel的文件所在位置
// Load config file (must import the storage module)
// 'scan': package containing Excel-mapped classes; Excel files auto-discovered in subdirs
// 'resource': path where Excel files are located
var context = new ClassPathXmlApplicationContext("application.xml");
// Excel的映射内容需要在被Spring管理的bean的方法上加上@ResInjection注解,即可自动注入Excel对应的对象
// 参考StudentManager中的标准用法
// Add @ResInjection to a Spring bean method to auto-inject Excel-mapped objects
// See StudentManager for standard usage
IStorage<Integer, StudentResource> storage1 = StorageContext.getStorageManager().getStorage(StudentResource.class);
//根据主键获取数据
//Get data by primary key
StudentResource studentResource = StorageContext.get(StudentResource.class, 1001);
TeacherResource teacherResource = StorageContext.get(TeacherResource.class, 1001);
//获取名字索引列表
//Get name index list
List<StudentResource> nameIndexList = StorageContext.getIndexes(StudentResource.class, StudentResource::getName, "james1");
//获取年龄索引列表
// Get age index list
List<StudentResource> ageIndexList = StorageContext.getIndexes(StudentResource.class, StudentResource::getAge, 10);
//获取Storage
//Get Storage instance
IStorage<Integer, StudentResource> storage = context.getBean(StorageManager.class).getStorage(StudentResource.class);
var studentManager = context.getBean(StudentManager.class);
var studentResources = studentManager.studentResources;
var studentCsvResources = studentManager.studentCsvResources;
// @Resource注解没指定别名,类名称和Excel名称必须完全一致,没有使用@ExcelFieldName对象属性名会自动对应同名的资源文件列名
// Without @Resource alias, class and Excel names must match; without @ExcelFieldName, fields map to same-named columns
for (StudentResource resource : studentResources.getAll()) {
logger.info(JsonUtils.object2String(resource));
}
System.out.println(StringUtils.MULTIPLE_HYPHENS);
// 通过id找到对应的行
// Find row by id
var id = 1002;
var valueById = studentResources.get(id);
logger.info(JsonUtils.object2String(valueById));
System.out.println(StringUtils.MULTIPLE_HYPHENS);
// 通过索引找对应的行
// Find row by index
var valuesByIndex = studentResources.getIndexes(StudentResource::getName, "james0");
logger.info(JsonUtils.object2String(valuesByIndex));
// 通过索引找对应的行
// Find row by index
var csvValuesByIndex = studentCsvResources.getIndexes(StudentCsvResource::getName, "james0");
logger.info(JsonUtils.object2String(csvValuesByIndex));
// Excel的映射内容需要在被Spring管理的bean的方法上加上@ResInjection注解,即可自动注入Excel对应的对象
// Add @ResInjection to a Spring bean method to auto-inject Excel-mapped objects
var testManager = context.getBean(TestManager.class);
var testResources = testManager.testResources;
for (com.zfoo.storage.resource.TestResource resource : testResources.getAll()) {
@@ -93,7 +93,7 @@ public class ApplicationTest {
AssertionUtils.notNull(map.get(1));
logger.info(JsonUtils.object2String(resource));
}
// 通过id找到对应的行
// Find row by id
id = 2;
var resource = testResources.get(id);
logger.info(JsonUtils.object2String(resource));
@@ -58,7 +58,7 @@ public class ConversionTest {
ConversionService conversionService = csfb.getObject();
//Json to Map
String str = "{\"1\":1,\"2\":2,\"3\":3}";
// 注意:第3个参数不能写成TypeDescriptor.valueOf(map.getClass()) 而是要明确指定Map的key和value的类型
// Note: 3rd arg must NOT be TypeDescriptor.valueOf(map.getClass()); specify key and value types explicitly
@SuppressWarnings("unchecked")
var map = (Map<Integer, Integer>) conversionService.convert(str, TypeDescriptor.valueOf(String.class), TypeDescriptor.map(HashMap.class, TypeDescriptor.valueOf(String.class), TypeDescriptor.valueOf(Integer.class)));
@@ -33,50 +33,50 @@ public class ExcelTesting {
@Test
public void createExcelTest() throws IOException {
//第一步创建workbook
//Step 1: create workbook
var wb = WorkbookFactory.create(true);
//第二步创建sheet
var sheet = wb.createSheet("测试");
//Step 2: create sheet
var sheet = wb.createSheet("TestSheet");
//第三步创建行row:添加表头0行
// Step 3: create header row (row 0)
var row = sheet.createRow(0);
var style = wb.createCellStyle();
//style.setAlignment(HSSFCellStyle.ALIGN_CENTER); //居中
//style.setAlignment(HSSFCellStyle.ALIGN_CENTER); //center alignment
//第四步创建单元格
var cell = row.createCell(0); //第一个单元格
cell.setCellValue("姓名");
//Step 4: create cells
var cell = row.createCell(0); //first cell
cell.setCellValue("Name");
cell.setCellStyle(style);
cell = row.createCell(1); //第二个单元格
cell.setCellValue("年龄");
cell = row.createCell(1); //second cell
cell.setCellValue("Age");
cell.setCellStyle(style);
//第五步插入数据
//Step 5: insert data
for (int i = 0; i < 5; i++) {
//创建行
//Create row
row = sheet.createRow(i + 1);
//创建单元格并且添加数据
//Create cells and add data
row.createCell(0).setCellValue("aa" + i);
row.createCell(1).setCellValue(i);
}
//第六步将生成excel文件保存到指定路径下
//Step 6: save Excel to the specified path
FileOutputStream target = new FileOutputStream("target.xlsx");
wb.write(target);
target.close();
System.out.println("Excel文件生成成功...");
System.out.println("Excel file generated successfully...");
}
@Test
public void readExcelTest() throws IOException {
Workbook wb = WorkbookFactory.create(new File("target.xlsx"));
// 只读取第一个工作簿
// Read only the first worksheet
Sheet sheet = wb.getSheetAt(0);
Iterator<Row> iterator = sheet.iterator();
@@ -91,7 +91,7 @@ public class ExcelTesting {
System.out.println(builder.toString());
}
System.out.println("Excel文件读取成功");
System.out.println("Excel file read successfully");
}
}
@@ -50,14 +50,14 @@ public class ExportBinaryTesting {
}
// 对应的excel表格,变量对应excel中的列,只有定义过的变量才能导出,没有定义的变量不会导出
// Variables correspond to Excel columns; only declared variables are exported
@Storage
public record StudentResource(
@Id
int id,
String idCard,
String name,
@AliasFieldName("年龄")
@AliasFieldName("Age")
int age,
float score,
String[] courses,
@@ -66,20 +66,20 @@ public class ExportBinaryTesting {
) {
}
// 需要定义一个类将所有的excel对象包裹起来
// Define a wrapper class for all Excel objects
public static class ResourceData {
public Map<Integer, StudentResource> studentResources;
}
// excel对应的java文件路径,比如ResourceData,StudentResource
// Java class path, e.g. ResourceData, StudentResource
public static final String excelClassPath = "com.zfoo.storage.export";
// excel文件路径,如果是类路径以classpath开头,如果是目录文件路径则以file开头,这里的excel文件在java代码的resources/excel目录里属于类路径
// Excel path: 'classpath:' for classpath, 'file:' for filesystem
public static final String excelFilePath = "classpath:/excel";
// excel输出的gdscript文件路径,会将excel转为gdscript对象
// Output GDScript file path
public static final String gdscriptOutputPath = "D:\\github\\godot-bird\\excel";
// excel导出的二进制文件路径
// Output binary file path
public static final String binaryOutputFilePath = "D:/github/godot-bird/excel/binary_data.cfg";
@@ -95,7 +95,7 @@ public class ExportBinaryTesting {
storageManager.initBefore();
storageManager.initAfter();
// 生成协议
// Generate protocol
var protocols = new ArrayList<Class<?>>();
protocols.add(ResourceData.class);
protocols.addAll(storageManager.storageMap().keySet());
@@ -104,7 +104,7 @@ public class ExportBinaryTesting {
operation.getGenerateLanguages().add(CodeLanguage.GdScript);
ProtocolManager.initProtocolAuto(protocols, operation);
// 生成数据
// Generate data
var resourceData = ExportUtils.autoWrapData(ResourceData.class, storageManager.storageMap());
var buffer = new UnpooledHeapByteBuf(ByteBufAllocator.DEFAULT, 100, Integer.MAX_VALUE);
ProtocolManager.write(buffer, resourceData);
@@ -34,7 +34,7 @@ public class ExportCsvTesting {
var outputDir = "E:\\workspace\\zfoo\\storage\\src\\test\\resources\\excel";
ExportUtils.excel2csv(inputDir, outputDir);
// godot4.x导入csv,莫名奇妙生成了一些translation文件,批量修改一下文件的名称
// Godot 4.x generates unexpected translation files when importing CSV; batch rename them
var csvFiles = ExportUtils.scanCsvFiles(outputDir);
csvFiles.stream().forEach(it -> it.renameTo(new File(StringUtils.format("{}.txt", it.getAbsolutePath()))));
csvFiles.stream().forEach(it -> FileUtils.deleteFile(it));
@@ -27,7 +27,7 @@ public class StudentCsvResource {
private int id;
/**
* 索引,默认为可重复的索引
* Index; duplicate entries allowed by default
*/
@Index
private String name;
@@ -39,7 +39,7 @@ public class StudentCsvResource {
private User user;
/**
* 不想映射的字段必须加上transient关键字,这样就不会从Excel中去找对应的列
* Fields not to be mapped to Excel must be marked with 'transient'
*/
private transient String notMapContent;
@@ -31,12 +31,12 @@ public class StudentResource {
private int id;
/**
* 索引,默认为可重复的索引
* Index; duplicate entries allowed by default
*/
@Index
private String name;
@Index
@AliasFieldName("年龄")
@AliasFieldName("Age")
private int age;
private float score;
private String[] courses;
@@ -47,7 +47,7 @@ public class StudentResource {
private List<User> userList;
private User user;
/**
* 不想映射的字段必须加上transient关键字,这样就不会从Excel中去找对应的列
* Fields not to be mapped to Excel must be marked with 'transient'
*/
private transient String notMapContent;
@@ -30,7 +30,7 @@ public record TeacherResource(
String idCard,
@Index
String name,
@AliasFieldName("年龄")
@AliasFieldName("Age")
int age,
float score,
String[] courses,