追加注释

This commit is contained in:
yuejiajun 2025-09-28 10:20:16 +08:00
parent b356735d72
commit 03ab867c4f
2 changed files with 246 additions and 32 deletions

View File

@ -12,39 +12,97 @@ import java.util.Map;
/**
* 模板文件工具类
* 负责从文件系统或类路径加载模板文件
* 负责从文件系统或类路径加载模板文件支持缓存机制提高性能
*
* <p>该类提供了以下功能</p>
* <ul>
* <li>从类路径加载模板文件</li>
* <li>从文件系统加载模板文件</li>
* <li>智能识别路径前缀自动选择加载方式</li>
* <li>模板内容缓存避免重复读取文件</li>
* <li>缓存管理功能</li>
* </ul>
*
* <p>使用示例</p>
* <pre>
* // 从类路径加载
* String template1 = TemplateFileLoader.loadTemplate("classpath:templates/default.html");
* // 从文件系统加载
* String template2 = TemplateFileLoader.loadTemplate("file:/opt/templates/default.html");
* // 默认从类路径加载
* String template3 = TemplateFileLoader.loadTemplate("templates/default.html");
* </pre>
*
* @author 岳佳君
* @version 1.0
*/
@Slf4j
public class TemplateFileLoader {
/**
* 默认模板目录路径
*/
private static final String DEFAULT_TEMPLATE_DIR = "templates/";
/**
* 类路径前缀标识符
*/
private static final String CLASS_PATH_PREFIX = "classpath:";
/**
* 文件系统路径前缀标识符
*/
private static final String FILE_PATH_PREFIX = "file:";
// 模板缓存
/**
* 模板缓存使用HashMap存储已加载的模板内容
* Key: 缓存键包含路径前缀和模板路径
* Value: 模板文件内容
*/
private static final Map<String, String> TEMPLATE_CACHE = new HashMap<>();
/**
* 从类路径加载模板文件
*
* <p>该方法会首先检查缓存中是否已存在该模板如果存在则直接返回缓存内容
* 否则从类路径读取文件内容并存入缓存</p>
*
* @param templatePath 模板文件路径可以是相对路径或绝对路径
* 相对路径相对于类路径根目录
* @return 模板文件的内容字符串
* @throws IOException 当模板文件未找到或读取失败时抛出
* @throws IllegalArgumentException 当templatePath为null或空字符串时抛出
*/
public static String loadTemplateFromClasspath(String templatePath) throws IOException {
// 参数校验
if (templatePath == null || templatePath.trim().isEmpty()) {
throw new IllegalArgumentException("模板路径不能为空");
}
String cacheKey = CLASS_PATH_PREFIX + templatePath;
// 检查缓存中是否已存在该模板
if (TEMPLATE_CACHE.containsKey(cacheKey)) {
log.debug("从缓存加载类路径模板: {}", templatePath);
return TEMPLATE_CACHE.get(cacheKey);
}
try {
// 从类路径加载
// 处理路径格式确保以"/"开头
String fullPath = templatePath.startsWith("/") ? templatePath : "/" + templatePath;
java.io.InputStream inputStream = TemplateFileLoader.class.getResourceAsStream(fullPath);
// 检查文件是否存在
if (inputStream == null) {
throw new IOException("模板文件未找到: " + templatePath);
}
// 读取文件内容并转换为字符串
String content = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8);
// 存入缓存
TEMPLATE_CACHE.put(cacheKey, content);
log.info("从类路径加载模板: {}", templatePath);
log.info("从类路径加载模板成功: {}", templatePath);
return content;
} catch (IOException e) {
@ -55,22 +113,43 @@ public class TemplateFileLoader {
/**
* 从文件系统加载模板文件
*
* <p>该方法会首先检查缓存中是否已存在该模板如果存在则直接返回缓存内容
* 否则从文件系统读取文件内容并存入缓存</p>
*
* @param filePath 模板文件的绝对路径或相对路径相对于当前工作目录
* @return 模板文件的内容字符串
* @throws IOException 当模板文件不存在或读取失败时抛出
* @throws IllegalArgumentException 当filePath为null或空字符串时抛出
*/
public static String loadTemplateFromFileSystem(String filePath) throws IOException {
// 参数校验
if (filePath == null || filePath.trim().isEmpty()) {
throw new IllegalArgumentException("文件路径不能为空");
}
String cacheKey = FILE_PATH_PREFIX + filePath;
// 检查缓存中是否已存在该模板
if (TEMPLATE_CACHE.containsKey(cacheKey)) {
log.debug("从缓存加载文件系统模板: {}", filePath);
return TEMPLATE_CACHE.get(cacheKey);
}
try {
Path path = Paths.get(filePath);
// 检查文件是否存在
if (!Files.exists(path)) {
throw new IOException("模板文件不存在: " + filePath);
}
// 读取文件内容
String content = Files.readString(path, StandardCharsets.UTF_8);
// 存入缓存
TEMPLATE_CACHE.put(cacheKey, content);
log.info("从文件系统加载模板: {}", filePath);
log.info("从文件系统加载模板成功: {}", filePath);
return content;
} catch (IOException e) {
@ -80,12 +159,31 @@ public class TemplateFileLoader {
}
/**
* 智能加载模板自动判断路径类型
* 智能加载模板文件自动判断路径类型
*
* <p>根据路径前缀自动选择加载方式</p>
* <ul>
* <li>"classpath:"开头从类路径加载</li>
* <li>"file:"开头从文件系统加载</li>
* <li>无前缀默认从类路径加载</li>
* </ul>
*
* @param templatePath 模板路径支持带前缀的完整路径或不带前缀的相对路径
* @return 模板文件的内容字符串
* @throws IOException 当模板文件加载失败时抛出
* @throws IllegalArgumentException 当templatePath为null或空字符串时抛出
*/
public static String loadTemplate(String templatePath) throws IOException {
// 参数校验
if (templatePath == null || templatePath.trim().isEmpty()) {
throw new IllegalArgumentException("模板路径不能为空");
}
if (templatePath.startsWith(CLASS_PATH_PREFIX)) {
// 从类路径加载去掉前缀
return loadTemplateFromClasspath(templatePath.substring(CLASS_PATH_PREFIX.length()));
} else if (templatePath.startsWith(FILE_PATH_PREFIX)) {
// 从文件系统加载去掉前缀
return loadTemplateFromFileSystem(templatePath.substring(FILE_PATH_PREFIX.length()));
} else {
// 默认从类路径加载
@ -94,17 +192,33 @@ public class TemplateFileLoader {
}
/**
* 清除模板缓存
* 清除所有模板缓存
*
* <p>该方法会清空当前内存中所有的模板缓存适用于需要强制重新加载模板的场景
* 比如模板文件内容已更新但缓存还未过期的情况</p>
*/
public static void clearCache() {
int cacheSize = TEMPLATE_CACHE.size();
TEMPLATE_CACHE.clear();
log.info("模板缓存已清除");
log.info("模板缓存已清除,原缓存大小: {}", cacheSize);
}
/**
* 获取缓存中的模板数量
* 获取当前缓存中的模板数量
*
* @return 缓存中模板的数量
*/
public static int getCacheSize() {
return TEMPLATE_CACHE.size();
}
}
/**
* 检查指定路径的模板是否已缓存
*
* @param templatePath 模板路径需要包含完整的前缀
* @return 如果模板已缓存返回true否则返回false
*/
public static boolean isCached(String templatePath) {
return TEMPLATE_CACHE.containsKey(templatePath);
}
}

View File

@ -13,34 +13,87 @@ import java.util.Map;
import java.util.Stack;
import java.util.regex.Pattern;
/**
* XML转AFSIM格式转换器
*
* <p>该类提供将XML文件转换为JSON格式并应用正则表达式规则进行键名转换的功能
* 采用流式处理方式适用于处理大型XML文件避免内存溢出</p>
*
* <p>主要功能</p>
* <ul>
* <li>流式解析XML文件降低内存占用</li>
* <li>支持正则表达式规则对JSON键名进行转换</li>
* <li>自动处理XML属性和元素内容</li>
* <li>生成标准JSON数组格式输出</li>
* </ul>
*
* @author 岳佳君
* @version 1.0
*/
@Slf4j
public class Xml2AFSIMTransformation {
/**
* 正则表达式规则映射
* Key: 编译后的正则表达式模式
* Value: 替换字符串
*/
private final Map<Pattern, String> regexRules;
/**
* 构造函数初始化转换规则
*
* @param rules 转换规则映射Key为匹配的正则表达式Value为替换字符串
* 如果为null则使用空规则集
*/
public Xml2AFSIMTransformation(Map<String, String> rules) {
log.info("初始化转换器,规则数量: {}", rules != null ? rules.size() : 0);
log.info("初始化XML转AFSIM转换器,规则数量: {}", rules != null ? rules.size() : 0);
// 编译正则表达式规则
regexRules = new HashMap<>();
if (rules != null) {
for (Map.Entry<String, String> entry : rules.entrySet()) {
// 预编译正则表达式以提高性能
regexRules.put(Pattern.compile(entry.getKey()), entry.getValue());
}
}
log.debug("正则表达式规则编译完成");
}
/**
* 流式XML转JSON并应用转换规则
* 将XML文件转换为JSON格式并应用转换规则
*
* <p>该方法使用StAX流式API解析XML适用于处理大型文件
* 转换后的JSON格式为对象数组每个XML根元素对应一个JSON对象</p>
*
* @param xmlFile 输入的XML文件不能为null
* @param jsonFile 输出的JSON文件不能为null
* @param rootElement 根元素名称用于标识每个JSON对象的开始和结束
* @throws Exception 当文件读写失败XML解析错误或转换过程中出现异常时抛出
* @throws IllegalArgumentException 当参数为null或空字符串时抛出
*/
public void convertXmlToJson(File xmlFile, File jsonFile, String rootElement) throws Exception {
// 参数校验
if (xmlFile == null || !xmlFile.exists()) {
throw new IllegalArgumentException("XML文件不存在或为null: " + xmlFile);
}
if (jsonFile == null) {
throw new IllegalArgumentException("JSON文件路径不能为null");
}
if (rootElement == null || rootElement.trim().isEmpty()) {
throw new IllegalArgumentException("根元素名称不能为空");
}
log.info("开始XML转JSON转换并应用规则");
log.debug("输入文件: {}, 输出文件: {}, 根元素: {}", xmlFile.getPath(), jsonFile.getPath(), rootElement);
try (FileInputStream xmlInputStream = new FileInputStream(xmlFile);
FileWriter jsonWriter = new FileWriter(jsonFile)) {
// 创建XML流读取器
XMLInputFactory factory = XMLInputFactory.newInstance();
XMLStreamReader reader = factory.createXMLStreamReader(xmlInputStream);
// 写入JSON数组开始标记
jsonWriter.write("[\n");
boolean firstObject = true;
@ -52,6 +105,7 @@ public class Xml2AFSIMTransformation {
StringBuilder currentText = new StringBuilder();
String currentElement = null;
// 流式解析XML
while (reader.hasNext()) {
int event = reader.next();
@ -80,7 +134,7 @@ public class Xml2AFSIMTransformation {
String text = reader.getText().trim();
if (!text.isEmpty()) {
currentText.append(text);
log.debug("文本内容: {}", text);
log.trace("文本内容: {}", text);
}
}
break;
@ -112,6 +166,7 @@ public class Xml2AFSIMTransformation {
jsonWriter.write(json);
objectCount++;
// 每处理100个对象刷新一次缓冲区并记录日志
if (objectCount % 100 == 0) {
log.info("已处理 {} 个对象", objectCount);
jsonWriter.flush();
@ -125,48 +180,68 @@ public class Xml2AFSIMTransformation {
}
}
// 写入JSON数组结束标记
jsonWriter.write("\n]");
log.info("转换完成,共处理 {} 个对象", objectCount);
} catch (Exception e) {
log.error("转换失败", e);
log.error("XML转JSON转换失败,文件: {}", xmlFile.getPath(), e);
throw e;
}
}
/**
* 处理元素的属性
* 处理XML元素的属性
*
* <p>将元素的属性添加到当前对象中属性键名为"元素名@属性名"格式</p>
*
* @param reader XML流读取器
* @param elementStack 元素栈用于跟踪当前解析路径
* @param currentObject 当前正在构建的对象
*/
private void processAttributesForElement(XMLStreamReader reader, Stack<String> elementStack,
Map<String, Object> currentObject) {
if (reader.getAttributeCount() > 0) {
for (int i = 0; i < reader.getAttributeCount(); i++) {
int attributeCount = reader.getAttributeCount();
if (attributeCount > 0) {
log.debug("处理元素 {} 的属性,属性数量: {}", elementStack.peek(), attributeCount);
for (int i = 0; i < attributeCount; i++) {
String attrName = reader.getAttributeLocalName(i);
String attrValue = reader.getAttributeValue(i);
// 默认处理将属性添加到对象中
// 将属性添加到对象中格式为元素名@属性名
String attrKey = elementStack.peek() + "@" + attrName;
currentObject.put(attrKey, attrValue);
log.trace("添加属性: {} = {}", attrKey, attrValue);
}
}
}
/**
* 处理元素文本内容
* 处理XML元素的文本内容
*
* <p>将元素的文本内容添加到当前对象中键名为完整的元素路径</p>
*
* @param elementName 元素名称
* @param content 元素文本内容
* @param elementStack 元素栈用于构建完整路径
* @param currentObject 当前正在构建的对象
*/
private void processElementContent(String elementName, String content,
Stack<String> elementStack, Map<String, Object> currentObject) {
// 默认处理将元素内容添加到对象中
// 构建完整的元素路径作为key
// 构建完整的元素路径作为key父元素.当前元素
String elementKey = String.join(".", elementStack) + "." + elementName;
currentObject.put(elementKey, content);
log.debug("添加元素内容: {} = {}", elementKey, content);
}
/**
* 将对象转换为JSON字符串
* 将Map对象转换为JSON字符串
*
* @param object 要转换的对象映射
* @return JSON格式的字符串如果对象为空则返回null
*/
private String convertObjectToJson(Map<String, Object> object) {
if (object.isEmpty()) {
log.warn("空对象,跳过输出");
if (object == null || object.isEmpty()) {
log.warn("空对象,跳过JSON转换");
return null;
}
@ -174,12 +249,15 @@ public class Xml2AFSIMTransformation {
return mapToJson(object);
} catch (Exception e) {
log.error("对象转JSON失败", e);
return "{}";
return "{}"; // 返回空对象作为容错处理
}
}
/**
* Map转JSON字符串
* 递归将Map转换为JSON字符串
*
* @param map 要转换的映射
* @return JSON对象字符串
*/
private String mapToJson(Map<String, Object> map) {
if (map.isEmpty()) {
@ -204,7 +282,10 @@ public class Xml2AFSIMTransformation {
}
/**
* 值转JSON
* 将值转换为JSON格式
*
* @param value 要转换的值支持StringNumberBooleanMapList等类型
* @return JSON格式的值表示
*/
private String valueToJson(Object value) {
if (value == null) {
@ -239,14 +320,21 @@ public class Xml2AFSIMTransformation {
return json.toString();
}
// 默认处理转换为字符串
return "\"" + escapeJson(value.toString()) + "\"";
}
/**
* JSON特殊字符转义
* JSON字符串转义处理
*
* <p>转义JSON中的特殊字符包括引号反斜杠控制字符等</p>
*
* @param text 要转义的文本
* @return 转义后的安全JSON字符串
*/
private String escapeJson(String text) {
if (text == null) return "";
return text.replace("\\", "\\\\")
.replace("\"", "\\\"")
.replace("\b", "\\b")
@ -258,6 +346,11 @@ public class Xml2AFSIMTransformation {
/**
* 应用正则表达式转换规则到单个对象
*
* <p>遍历对象的所有键对每个键应用正则表达式规则进行转换</p>
*
* @param originalMap 原始对象映射
* @return 应用规则转换后的新对象映射
*/
private Map<String, Object> applyRules(Map<String, Object> originalMap) {
Map<String, Object> transformedMap = new HashMap<>();
@ -270,25 +363,32 @@ public class Xml2AFSIMTransformation {
// 如果找到匹配的规则则使用新key否则保留原key
if (newKey != null) {
transformedMap.put(newKey, value);
log.debug("转换: {} -> {}", originalKey, newKey);
log.debug("键名转换: {} -> {}", originalKey, newKey);
} else {
transformedMap.put(originalKey, value);
}
}
log.debug("规则应用完成,原始键数: {}, 转换后键数: {}", originalMap.size(), transformedMap.size());
return transformedMap;
}
/**
* 查找并替换匹配的正则规则
*
* <p>遍历所有正则规则找到第一个匹配的规则并进行替换</p>
*
* @param originalKey 原始键名
* @return 替换后的新键名如果没有匹配规则则返回null
*/
private String findAndReplace(String originalKey) {
for (Map.Entry<Pattern, String> rule : regexRules.entrySet()) {
if (rule.getKey().matcher(originalKey).matches()) {
return rule.getKey().matcher(originalKey).replaceAll(rule.getValue());
String newKey = rule.getKey().matcher(originalKey).replaceAll(rule.getValue());
log.trace("正则匹配: {} -> {} (规则: {})", originalKey, newKey, rule.getKey().pattern());
return newKey;
}
}
return null;
}
}
}