package cn.iocoder.yudao.module.qcreport.engine;
import java.math.BigDecimal;
import java.time.temporal.TemporalAccessor;
import java.time.format.DateTimeFormatter;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* 上下文取值。
*
* 只做白名单式的属性导航,不解析、不执行任何代码:
* 支持 a.b.c 与 a.b[0].c 两种写法,路径在数组上继续取属性时按元素逐个取值(pluck)。
*
* 与前端 {@code src/components/quality/engine/path.ts} 一一对应,两边必须同语义。
*/
public final class Paths {
/** 路径片段:普通属性名或数组下标 */
private static final Pattern TOKEN_PATTERN = Pattern.compile("[^.\\[\\]]+");
/** 禁止访问的属性名,避免顺着原型链读到构造器 */
private static final Set BLOCKED_KEYS = Set.of("__proto__", "constructor", "prototype");
private static final Pattern INTEGER_TEXT = Pattern.compile("^\\d+$");
private static final DateTimeFormatter DATE_TIME_FORMAT =
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
private Paths() {
}
/**
* 把路径拆成片段:inspectionItems[0].actualValue → [inspectionItems, 0, actualValue]
*/
public static List parsePath(String path) {
List tokens = new ArrayList<>();
if (path == null) {
return tokens;
}
Matcher matcher = TOKEN_PATTERN.matcher(path);
while (matcher.find()) {
tokens.add(matcher.group());
}
return tokens;
}
/**
* 按路径取值,取不到返回 null。
*/
public static Object readPath(Object source, String path) {
return readTokens(source, parsePath(path), 0);
}
private static Object readTokens(Object source, List tokens, int start) {
Object current = source;
for (int index = start; index < tokens.size(); index++) {
String token = tokens.get(index);
if (current == null) {
return null;
}
if (current instanceof List> list) {
Integer arrayIndex = toIndex(token);
if (arrayIndex == null) {
// 数组上继续取属性:逐元素取值,得到同长度的数组
List