From 9ff52ed15ec136fcb00eb7dc7b0d4ea1af75a845 Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Sat, 7 Jun 2025 03:33:43 +0800 Subject: [PATCH 1/3] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E6=A8=A1=E6=9D=BF?= =?UTF-8?q?=E7=B3=BB=E7=BB=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/resources/translations/en.yml | 1 + .../src/main/resources/translations/zh_cn.yml | 1 + .../core/pack/AbstractPackManager.java | 10 +- .../template/ExpressionTemplateArgument.java | 66 ++ .../config/template/ListTemplateArgument.java | 2 +- .../config/template/MapTemplateArgument.java | 2 +- .../config/template/NullTemplateArgument.java | 2 +- .../template/ObjectTemplateArgument.java | 8 +- .../template/PlainStringTemplateArgument.java | 2 +- .../SelfIncreaseIntTemplateArgument.java | 2 +- .../config/template/TemplateArgument.java | 6 +- .../config/template/TemplateArguments.java | 2 + .../config/template/TemplateManager.java | 273 +++++++- .../config/template/TemplateManagerImpl.java | 655 ++++++------------ .../craftengine/core/util/MiscUtils.java | 16 + gradle.properties | 2 +- 16 files changed, 592 insertions(+), 458 deletions(-) create mode 100644 core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/ExpressionTemplateArgument.java diff --git a/common-files/src/main/resources/translations/en.yml b/common-files/src/main/resources/translations/en.yml index 4d46ce56c..3f2cfa367 100644 --- a/common-files/src/main/resources/translations/en.yml +++ b/common-files/src/main/resources/translations/en.yml @@ -132,6 +132,7 @@ warning.config.recipe.smithing_transform.post_processor.keep_component.missing_c warning.config.recipe.smithing_transform.post_processor.keep_component.missing_tags: "Issue found in file - The smithing transform recipe '' is missing the required argument 'tags' for post-processors 'keep_tags'." warning.config.i18n.unknown_locale: "Issue found in file - Unknown locale ''." warning.config.template.duplicate: "Issue found in file - Duplicated template ''. Please check if there is the same configuration in other files." +warning.config.template.invalid: "Issue found in file - The config '' is using an invalid template ''." warning.config.template.argument.self_increase_int.invalid_range: "Issue found in file - The template '' is using a 'from' '' larger than 'to' '' in 'self_increase_int' argument." warning.config.template.argument.list.invalid_type: "Issue found in file - The template '' is using a 'list' argument which expects a 'List' as argument while the input argument is a(n) ''." warning.config.vanilla_loot.missing_type: "Issue found in file - The vanilla loot '' is missing the required 'type' argument." diff --git a/common-files/src/main/resources/translations/zh_cn.yml b/common-files/src/main/resources/translations/zh_cn.yml index 9280141c9..a065df8fb 100644 --- a/common-files/src/main/resources/translations/zh_cn.yml +++ b/common-files/src/main/resources/translations/zh_cn.yml @@ -133,6 +133,7 @@ warning.config.recipe.smithing_transform.post_processor.keep_component.missing_t warning.config.i18n.unknown_locale: "在文件 发现问题 - 未知的语言环境 ''" warning.config.template.duplicate: "在文件 发现问题 - 重复的模板 '' 请检查其他文件中是否存在相同配置" warning.config.template.argument.self_increase_int.invalid_range: "在文件 发现问题 - 模板 '' 在 'self_increase_int' 参数中使用了一个起始值 '' 大于终止值 ''" +warning.config.template.invalid: "在文件 发现问题 - 配置 '' 使用了无效的模板 ''." warning.config.template.argument.list.invalid_type: "在文件 发现问题 - 模板 '' 的 'list' 参数需要列表类型 但输入参数类型为 ''" warning.config.vanilla_loot.missing_type: "在文件 发现问题 - 原版战利品 '' 缺少必需的 'type' 参数" warning.config.vanilla_loot.invalid_type: "在文件 发现问题 - 原版战利品 '' 使用了无效类型 '' 允许的类型: []" diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java b/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java index 4ec3ffd9b..bbfe9f75f 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java @@ -467,7 +467,8 @@ public abstract class AbstractPackManager implements PackManager { } for (Map.Entry entry : cachedFile.config().entrySet()) { processConfigEntry(entry, path, cachedFile.pack(), (p, c) -> - cachedConfigs.computeIfAbsent(p, k -> new ArrayList<>()).add(c)); + cachedConfigs.computeIfAbsent(p, k -> new ArrayList<>()).add(c) + ); } } return FileVisitResult.CONTINUE; @@ -494,12 +495,13 @@ public abstract class AbstractPackManager implements PackManager { Key id = Key.withDefaultNamespace(key, cached.pack().namespace()); try { if (parser.supportsParsingObject()) { + // do not apply templates parser.parseObject(cached.pack(), cached.filePath(), id, configEntry.getValue()); } else if (predicate.test(parser)) { if (configEntry.getValue() instanceof Map configSection0) { - Map configSection1 = castToMap(configSection0, false); - if ((boolean) configSection1.getOrDefault("enable", true)) { - parser.parseSection(cached.pack(), cached.filePath(), id, plugin.templateManager().applyTemplates(id, configSection1)); + Map config = castToMap(configSection0, false); + if ((boolean) config.getOrDefault("enable", true)) { + parser.parseSection(cached.pack(), cached.filePath(), id, MiscUtils.castToMap(this.plugin.templateManager().applyTemplates(id, config), false)); } } else { TranslationManager.instance().log("warning.config.structure.not_section", cached.filePath().toString(), cached.prefix() + "." + key, configEntry.getValue().getClass().getSimpleName()); diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/ExpressionTemplateArgument.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/ExpressionTemplateArgument.java new file mode 100644 index 000000000..b2da9c6a9 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/ExpressionTemplateArgument.java @@ -0,0 +1,66 @@ +package net.momirealms.craftengine.core.plugin.config.template; + +import com.ezylang.evalex.Expression; +import com.ezylang.evalex.data.EvaluationValue; +import net.momirealms.craftengine.core.util.Key; + +import java.util.Locale; +import java.util.Map; +import java.util.Optional; +import java.util.function.Function; + +public class ExpressionTemplateArgument implements TemplateArgument { + public static final Factory FACTORY = new Factory(); + private final TemplateManager.ArgumentString expression; + private final ValueType valueType; + + protected ExpressionTemplateArgument(String expression, ValueType valueType) { + this.expression = TemplateManager.preParse(expression); + this.valueType = valueType; + } + + @Override + public Object get(Map arguments) { + String expression = Optional.ofNullable(this.expression.get(arguments)).map(String::valueOf).orElse(null); + if (expression == null) return null; + try { + return this.valueType.formatter().apply(new Expression(expression).evaluate()); + } catch (Exception e) { + throw new RuntimeException("Failed to process expression argument: " + this.expression, e); + } + } + + @Override + public Key type() { + return TemplateArguments.EXPRESSION; + } + + protected enum ValueType { + INT(e -> e.getNumberValue().intValue()), + LONG(e -> e.getNumberValue().longValue()), + SHORT(e -> e.getNumberValue().shortValueExact()), + DOUBLE(e -> e.getNumberValue().doubleValue()), + FLOAT(e -> e.getNumberValue().floatValue()), + BOOLEAN(EvaluationValue::getBooleanValue),; + + private final Function formatter; + + ValueType(Function formatter) { + this.formatter = formatter; + } + + public Function formatter() { + return formatter; + } + } + + public static class Factory implements TemplateArgumentFactory { + @Override + public TemplateArgument create(Map arguments) { + return new ExpressionTemplateArgument( + arguments.getOrDefault("expression", "").toString(), + ValueType.valueOf(arguments.getOrDefault("value-type", "double").toString().toUpperCase(Locale.ENGLISH)) + ); + } + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/ListTemplateArgument.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/ListTemplateArgument.java index 0ed041519..eae376ac0 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/ListTemplateArgument.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/ListTemplateArgument.java @@ -16,7 +16,7 @@ public class ListTemplateArgument implements TemplateArgument { } @Override - public List get() { + public List get(Map arguments) { return value; } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/MapTemplateArgument.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/MapTemplateArgument.java index c037578fb..9df939320 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/MapTemplateArgument.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/MapTemplateArgument.java @@ -14,7 +14,7 @@ public class MapTemplateArgument implements TemplateArgument { } @Override - public Map get() { + public Map get(Map arguments) { return value; } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/NullTemplateArgument.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/NullTemplateArgument.java index fed348953..8a568d2af 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/NullTemplateArgument.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/NullTemplateArgument.java @@ -17,7 +17,7 @@ public class NullTemplateArgument implements TemplateArgument { } @Override - public Object get() { + public Object get(Map arguments) { return null; } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/ObjectTemplateArgument.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/ObjectTemplateArgument.java index 51e6b07e0..e78af6b66 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/ObjectTemplateArgument.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/ObjectTemplateArgument.java @@ -2,6 +2,8 @@ package net.momirealms.craftengine.core.plugin.config.template; import net.momirealms.craftengine.core.util.Key; +import java.util.Map; + public class ObjectTemplateArgument implements TemplateArgument { private final Object value; @@ -9,13 +11,17 @@ public class ObjectTemplateArgument implements TemplateArgument { this.value = value; } + public static ObjectTemplateArgument of(Object value) { + return new ObjectTemplateArgument(value); + } + @Override public Key type() { return TemplateArguments.OBJECT; } @Override - public Object get() { + public Object get(Map arguments) { return this.value; } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/PlainStringTemplateArgument.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/PlainStringTemplateArgument.java index a8017fcf5..a9ef8d6ae 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/PlainStringTemplateArgument.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/PlainStringTemplateArgument.java @@ -17,7 +17,7 @@ public class PlainStringTemplateArgument implements TemplateArgument { } @Override - public String get() { + public String get(Map arguments) { return value; } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/SelfIncreaseIntTemplateArgument.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/SelfIncreaseIntTemplateArgument.java index 92772099d..d01b87124 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/SelfIncreaseIntTemplateArgument.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/SelfIncreaseIntTemplateArgument.java @@ -19,7 +19,7 @@ public class SelfIncreaseIntTemplateArgument implements TemplateArgument { } @Override - public String get() { + public String get(Map arguments) { String value = String.valueOf(this.current); if (this.current < this.max) this.current += 1; return value; diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/TemplateArgument.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/TemplateArgument.java index 87181291d..9816cfebc 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/TemplateArgument.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/TemplateArgument.java @@ -2,9 +2,11 @@ package net.momirealms.craftengine.core.plugin.config.template; import net.momirealms.craftengine.core.util.Key; -import java.util.function.Supplier; +import java.util.Map; -public interface TemplateArgument extends Supplier { +public interface TemplateArgument { Key type(); + + Object get(Map arguments); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/TemplateArguments.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/TemplateArguments.java index f437a7493..0586843f5 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/TemplateArguments.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/TemplateArguments.java @@ -15,6 +15,7 @@ public class TemplateArguments { public static final Key MAP = Key.of("craftengine:map"); public static final Key LIST = Key.of("craftengine:list"); public static final Key NULL = Key.of("craftengine:null"); + public static final Key EXPRESSION = Key.of("craftengine:expression"); public static final Key OBJECT = Key.of("craftengine:object"); // No Factory, internal use public static void register(Key key, TemplateArgumentFactory factory) { @@ -29,6 +30,7 @@ public class TemplateArguments { register(MAP, MapTemplateArgument.FACTORY); register(LIST, ListTemplateArgument.FACTORY); register(NULL, NullTemplateArgument.FACTORY); + register(EXPRESSION, ExpressionTemplateArgument.FACTORY); } public static TemplateArgument fromMap(Map map) { diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/TemplateManager.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/TemplateManager.java index 4313066d2..fa21e3e14 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/TemplateManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/TemplateManager.java @@ -4,19 +4,274 @@ import net.momirealms.craftengine.core.plugin.Manageable; import net.momirealms.craftengine.core.plugin.config.ConfigParser; import net.momirealms.craftengine.core.util.Key; +import java.util.ArrayList; +import java.util.List; import java.util.Map; -import java.util.regex.Pattern; public interface TemplateManager extends Manageable { - Pattern ARGUMENT_PATTERN = Pattern.compile("\\{[^{}]+}"); - String LEFT_BRACKET = "{"; - String RIGHT_BRACKET = "}"; - String TEMPLATE = "template"; - String OVERRIDES = "overrides"; - String ARGUMENTS = "arguments"; - String MERGES = "merges"; ConfigParser parser(); - Map applyTemplates(Key id, Map input); + Object applyTemplates(Key id, Object input); + + interface ArgumentString { + + String rawValue(); + + Object get(Map arguments); + } + + final class Literal implements ArgumentString { + private final String value; + + public Literal(String value) { + this.value = value; + } + + public static Literal literal(String value) { + return new Literal(value); + } + + @Override + public String rawValue() { + return this.value; + } + + @Override + public Object get(Map arguments) { + return this.value; + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof Literal literal)) return false; + return this.value.equals(literal.value); + } + + @Override + public int hashCode() { + return this.value.hashCode(); + } + + @Override + public String toString() { + return "Literal(" + this.value + ")"; + } + } + + final class Placeholder implements ArgumentString { + private final String placeholder; + private final String rawText; + + public Placeholder(String placeholder) { + this.placeholder = placeholder; + this.rawText = "{" + this.placeholder + "}"; + } + + public static Placeholder placeholder(String placeholder) { + return new Placeholder(placeholder); + } + + @Override + public Object get(Map arguments) { + TemplateArgument replacement = arguments.get(this.placeholder); + if (replacement != null) { + return replacement.get(arguments); + } + return rawValue(); + } + + @Override + public String rawValue() { + return this.rawText; + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof Placeholder that)) return false; + return this.placeholder.equals(that.placeholder); + } + + @Override + public int hashCode() { + return this.placeholder.hashCode(); + } + + @Override + public String toString() { + return "Placeholder(" + this.placeholder + ")"; + } + } + + final class Complex2 implements ArgumentString { + private final String rawText; + private final ArgumentString arg1; + private final ArgumentString arg2; + + public Complex2(String rawText, ArgumentString arg1, ArgumentString arg2) { + this.arg1 = arg1; + this.arg2 = arg2; + this.rawText = rawText; + } + + @Override + public Object get(Map arguments) { + Object arg1 = this.arg1.get(arguments); + Object arg2 = this.arg2.get(arguments); + if (arg1 == null && arg2 == null) return null; + if (arg1 == null) return String.valueOf(arg2); + if (arg2 == null) return String.valueOf(arg1); + return String.valueOf(arg1) + arg2; + } + + @Override + public String rawValue() { + return this.rawText; + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof Complex that)) return false; + return this.rawText.equals(that.rawText); + } + + @Override + public int hashCode() { + return this.rawText.hashCode(); + } + + @Override + public String toString() { + return "Complex2(" + this.rawText + ")"; + } + } + + final class Complex implements ArgumentString { + private final List parts; + private final String rawText; + + public Complex(String rawText, List parts) { + this.parts = parts; + this.rawText = rawText; + } + + @Override + public Object get(Map arguments) { + StringBuilder result = new StringBuilder(); + boolean hasValue = false; + for (ArgumentString part : this.parts) { + Object arg = part.get(arguments); + if (arg != null) { + result.append(arg); + hasValue = true; + } + } + if (!hasValue) return null; + return result.toString(); + } + + @Override + public String rawValue() { + return this.rawText; + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof Complex that)) return false; + return this.rawText.equals(that.rawText); + } + + @Override + public int hashCode() { + return this.rawText.hashCode(); + } + + @Override + public String toString() { + return "Complex(" + this.rawText + ")"; + } + } + + static ArgumentString preParse(String input) { + if (input == null || input.isEmpty()) { + return Literal.literal(""); + } + int n = input.length(); + int lastAppendPosition = 0; // 追踪上一次追加操作结束的位置 + int i = 0; + + List arguments = new ArrayList<>(); + while (i < n) { + // 检查当前字符是否为未转义的 '{' + int backslashes = 0; + int temp_i = i - 1; + while (temp_i >= 0 && input.charAt(temp_i) == '\\') { + backslashes++; + temp_i--; + } + if (input.charAt(i) == '{' && backslashes % 2 == 0) { + // 发现占位符起点 + int placeholderStartIndex = i; + // 追加从上一个位置到当前占位符之前的文本 + if (lastAppendPosition < i) { + arguments.add(Literal.literal(input.substring(lastAppendPosition, i))); + } + // --- 开始解析占位符内部 --- + StringBuilder keyBuilder = new StringBuilder(); + int depth = 1; + int j = i + 1; + boolean foundMatch = false; + while (j < n) { + char c = input.charAt(j); + if (c == '\\') { // 处理转义 + if (j + 1 < n) { + keyBuilder.append(input.charAt(j + 1)); + j += 2; + } else { + keyBuilder.append(c); + j++; + } + } else if (c == '{') { + depth++; + keyBuilder.append(c); + j++; + } else if (c == '}') { + depth--; + if (depth == 0) { // 找到匹配的结束括号 + String key = keyBuilder.toString(); + arguments.add(Placeholder.placeholder(key)); + + // 更新位置指针 + i = j + 1; + lastAppendPosition = i; + foundMatch = true; + break; + } + keyBuilder.append(c); // 嵌套的 '}' + j++; + } else { + keyBuilder.append(c); + j++; + } + } + // --- 占位符解析结束 --- + if (!foundMatch) { + // 如果内层循环结束仍未找到匹配的 '}',则不进行任何特殊处理 + // 外层循环的 i 会自然递增 + i++; + } + } else { + i++; + } + } + // 追加最后一个占位符之后的所有剩余文本 + if (lastAppendPosition < n) { + arguments.add(Literal.literal(input.substring(lastAppendPosition))); + } + return switch (arguments.size()) { + case 1 -> arguments.getFirst(); + case 2 -> new Complex2(input, arguments.get(0), arguments.get(1)); + default -> new Complex(input, arguments); + }; + } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/TemplateManagerImpl.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/TemplateManagerImpl.java index c020036ab..57ef4f95c 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/TemplateManagerImpl.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/TemplateManagerImpl.java @@ -4,30 +4,38 @@ import net.momirealms.craftengine.core.pack.LoadingSequence; import net.momirealms.craftengine.core.pack.Pack; import net.momirealms.craftengine.core.plugin.config.ConfigParser; import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException; -import net.momirealms.craftengine.core.util.GsonHelper; import net.momirealms.craftengine.core.util.Key; import net.momirealms.craftengine.core.util.MiscUtils; import org.jetbrains.annotations.NotNull; import java.nio.file.Path; import java.util.*; -import java.util.function.Consumer; -import java.util.function.Supplier; -import java.util.regex.Matcher; @SuppressWarnings("DuplicatedCode") public class TemplateManagerImpl implements TemplateManager { - /* - * 此类仍需要一次重构,对模板预解析,避免每次调用时候重新判断值是否含有参数 - */ + private static final ArgumentString TEMPLATE = Literal.literal("template"); + private static final ArgumentString OVERRIDES = Literal.literal("overrides"); + private static final ArgumentString ARGUMENTS = Literal.literal("arguments"); + private static final ArgumentString MERGES = Literal.literal("merges"); + private final static Set NON_TEMPLATE_ARGUMENTS = new HashSet<>(Set.of(TEMPLATE, ARGUMENTS, OVERRIDES, MERGES)); + private final Map templates = new HashMap<>(); - private final static Set NON_TEMPLATE_KEY = new HashSet<>(Set.of(TEMPLATE, ARGUMENTS, OVERRIDES, MERGES)); private final TemplateParser templateParser; public TemplateManagerImpl() { this.templateParser = new TemplateParser(); } + @Override + public void unload() { + this.templates.clear(); + } + + @Override + public ConfigParser parser() { + return this.templateParser; + } + public class TemplateParser implements ConfigParser { public static final String[] CONFIG_SECTION_NAME = new String[] {"templates", "template"}; @@ -51,413 +59,270 @@ public class TemplateManagerImpl implements TemplateManager { if (templates.containsKey(id)) { throw new LocalizedResourceConfigException("warning.config.template.duplicate", path.toString(), id.toString()); } - templates.put(id, obj); + // 预处理会将 string类型的键或值解析为ArgumentString,以加速模板应用。所以处理后不可能存在String类型。 + templates.put(id, preprocessUnknownValue(obj)); } } @Override - public void unload() { - this.templates.clear(); + public Object applyTemplates(Key id, Object input) { + Object preprocessedInput = preprocessUnknownValue(input); + return processUnknownValue(preprocessedInput, Map.of( + "__NAMESPACE__", PlainStringTemplateArgument.plain(id.namespace()), + "__ID__", PlainStringTemplateArgument.plain(id.value()) + )); } - @Override - public ConfigParser parser() { - return this.templateParser; - } - - @Override - public Map applyTemplates(Key id, Map input) { - Objects.requireNonNull(input, "Input must not be null"); - Map result = new LinkedHashMap<>(); - processMap(input, - Map.of("__ID__", PlainStringTemplateArgument.plain(id.value()), - "__NAMESPACE__", PlainStringTemplateArgument.plain(id.namespace())), - (obj) -> { - // 当前位于根节点下,如果下一级就是模板,则应把模板结果与当前map合并 - // 如果模板结果不是map,则为非法值,因为不可能出现类似于下方的配置 - // items: - // test:invalid: 111 - if (obj instanceof Map mapResult) { - result.putAll(MiscUtils.castToMap(mapResult, false)); - } else { - throw new IllegalArgumentException("Invalid template used. Input: " + GsonHelper.get().toJson(input) + ". Template: " + GsonHelper.get().toJson(obj)); - } - }); - return result; + private Object preprocessUnknownValue(Object value) { + switch (value) { + case Map map -> { + Map in = MiscUtils.castToMap(map, false); + Map out = new LinkedHashMap<>(map.size()); + for (Map.Entry entry : in.entrySet()) { + out.put(TemplateManager.preParse(entry.getKey()), preprocessUnknownValue(entry.getValue())); + } + return out; + } + case List list -> { + List objList = new ArrayList<>(list.size()); + for (Object o : list) { + objList.add(preprocessUnknownValue(o)); + } + return objList; + } + case String string -> { + return TemplateManager.preParse(string); + } + case null, default -> { + return value; + } + } } // 对于处理map,只有input是已知map,而返回值可能并不是 - private void processMap(Map input, - Map parentArguments, - // 只有当前为模板的时候,才会调用callback - Consumer processCallBack) { + private Object processMap(Map input, + Map arguments) { // 传入的input是否含有template,这种情况下,返回值有可能是非map if (input.containsKey(TEMPLATE)) { - TemplateProcessingResult processingResult = processTemplates(input, parentArguments); - List templates = processingResult.templates(); - // 你敢保证template里没有template吗? - List processedTemplates = new ArrayList<>(); - // 先递归处理后再合并 - for (Object template : templates) { - processUnknownTypeMember(template, processingResult.arguments(), processedTemplates::add); - } - if (processedTemplates.isEmpty()) { - return; - } - Object firstTemplate = processedTemplates.get(0); - // 如果是map,应当深度合并 - if (firstTemplate instanceof Map) { - Map results = new LinkedHashMap<>(); - for (Object processedTemplate : processedTemplates) { - if (processedTemplate instanceof Map anotherMap) { - deepMergeMaps(results, MiscUtils.castToMap(anotherMap, false)); + TemplateProcessingResult processingResult = processTemplates(input, arguments); + List processedTemplates = processingResult.templates(); + if (!processedTemplates.isEmpty()) { + // 先获取第一个模板的类型 + Object firstTemplate = processedTemplates.getFirst(); + // 如果是map,应当深度合并 + if (firstTemplate instanceof Map) { + Map results = new LinkedHashMap<>(); + for (Object processedTemplate : processedTemplates) { + if (processedTemplate instanceof Map map) { + deepMergeMaps(results, MiscUtils.castToMap(map, false)); + } } - } - if (processingResult.overrides() instanceof Map overrides) { - results.putAll(MiscUtils.castToMap(overrides, false)); - } - if (processingResult.merges() instanceof Map merges) { - deepMergeMaps(results, MiscUtils.castToMap(merges, false)); - } - processCallBack.accept(results); - } else if (firstTemplate instanceof List) { - List results = new ArrayList<>(); - // 仅仅合并list - for (Object processedTemplate : processedTemplates) { - if (processedTemplate instanceof List anotherList) { - results.addAll(anotherList); + if (processingResult.overrides() instanceof Map overrides) { + results.putAll(MiscUtils.castToMap(overrides, false)); } - } - if (processingResult.overrides() instanceof List overrides) { - results.clear(); - results.addAll(overrides); - } - if (processingResult.merges() instanceof List merges) { - results.addAll(merges); - } - processCallBack.accept(results); - } else { - Object overrides = processingResult.overrides(); - if (overrides != null) { - processCallBack.accept(overrides); + if (processingResult.merges() instanceof Map merges) { + deepMergeMaps(results, MiscUtils.castToMap(merges, false)); + } + return results; + } else if (firstTemplate instanceof List) { + List results = new ArrayList<>(); + // 仅仅合并list + for (Object processedTemplate : processedTemplates) { + if (processedTemplate instanceof List anotherList) { + results.addAll(anotherList); + } + } + if (processingResult.overrides() instanceof List overrides) { + results.clear(); + results.addAll(overrides); + } + if (processingResult.merges() instanceof List merges) { + results.addAll(merges); + } + return results; } else { - // 其他情况下应当忽略其他的template - processCallBack.accept(firstTemplate); + // 有覆写用覆写,无覆写返回最后一个模板值 + if (processingResult.overrides() != null) { + return processingResult.overrides(); + } + if (processingResult.merges() != null) { + return processingResult.merges(); + } + return processedTemplates.getLast(); } + } else { + // 模板为空啦,如果是map,则合并 + if (processingResult.overrides() instanceof Map overrides) { + Map output = new LinkedHashMap<>(MiscUtils.castToMap(overrides, false)); + if (processingResult.merges() instanceof Map merges) { + deepMergeMaps(output, MiscUtils.castToMap(merges, false)); + } + return output; + } else if (processingResult.overrides() instanceof List overrides) { + List output = new ArrayList<>(overrides); + if (processingResult.merges() instanceof List merges) { + output.addAll(merges); + } + return output; + } + // 否则有overrides就返回overrides + if (processingResult.overrides() != null) { + return processingResult.overrides(); + } + // 否则有merges就返回merges + if (processingResult.merges() != null) { + return processingResult.merges(); + } + return null; } } else { // 如果不是模板,则返回值一定是map // 依次处理map下的每个参数 - Map result = new LinkedHashMap<>(); - for (Map.Entry inputEntry : input.entrySet()) { - String key = applyArgument(inputEntry.getKey(), parentArguments).toString(); - processUnknownTypeMember(inputEntry.getValue(), parentArguments, (processed) -> result.put(key, processed)); + Map result = new LinkedHashMap<>(input.size()); + for (Map.Entry inputEntry : input.entrySet()) { + Object key = inputEntry.getKey().get(arguments); + // 如果key为null说明不插入此键 + if (key != null) { + result.put(key.toString(), processUnknownValue(inputEntry.getValue(), arguments)); + } } - processCallBack.accept(result); + return result; } } - // 处理一个类型未知的值,本方法只管将member处理好后,传递回调用者 - private void processUnknownTypeMember(Object member, - Map parentArguments, - Consumer processCallback) { - if (member instanceof Map innerMap) { + // 处理一个类型未知的值,本方法只管将member处理好后,传递回调用者a + @SuppressWarnings("unchecked") + private Object processUnknownValue(Object value, + Map arguments) { + switch (value) { + case Map innerMap -> // map下面还是个map吗?这并不一定 - // 比如 - // a: - // template: xxx - // 这时候a并不一定是map,最终类型取决于template,那么应当根据template的结果进行调整,所以我们继续交给上方方法处理 - processMap(MiscUtils.castToMap(innerMap, false), parentArguments, processCallback); - } else if (member instanceof List innerList) { - // map 下面是个list,那么对下面的每个成员再次处理 - List result = new ArrayList<>(); - for (Object item : innerList) { - // 处理完以后,加入到list内 - processUnknownTypeMember(item, parentArguments, result::add); + // 这时候并不一定是map,最终类型取决于template,那么应当根据template的结果进行调整,所以我们继续交给上方方法处理 + { + return processMap((Map) innerMap, arguments); + } + case List innerList -> { + List result = new ArrayList<>(); + for (Object item : innerList) { + result.add(processUnknownValue(item, arguments)); + } + return result; + } + case ArgumentString arg -> { + return arg.get(arguments); + } + case null, default -> { + return value; } - processCallback.accept(result); - } else if (member instanceof String possibleArgument) { - // 如果是个string,其可能是 {xxx} 的参数,那么就尝试应用参数后再返回 - processCallback.accept(applyArgument(possibleArgument, parentArguments)); - } else { - // 对于其他值,直接处理 - processCallback.accept(member); } } - private TemplateProcessingResult processTemplates(Map input, + @SuppressWarnings("unchecked") + private TemplateProcessingResult processTemplates(Map input, Map parentArguments) { + int knownKeys = 1; // 先获取template节点下所有的模板 - List templateIds = MiscUtils.getAsStringList(input.get(TEMPLATE)); + List templateIds = MiscUtils.getAsList(input.get(TEMPLATE), ArgumentString.class); List templateList = new ArrayList<>(templateIds.size()); - for (String templateId : templateIds) { - // 如果模板id被用了参数,则应先应用参数后再查询模板 - Object actualTemplate = applyArgument(templateId, parentArguments); - if (actualTemplate == null) continue; // 忽略被null掉的模板 - Object template = Optional.ofNullable(this.templates.get(Key.of(actualTemplate.toString()))) - .orElseThrow(() -> new IllegalArgumentException("Template not found: " + actualTemplate)); - templateList.add(template); - } - + // 获取arguments Object argument = input.get(ARGUMENTS); boolean hasArgument = argument != null; + if (hasArgument) knownKeys++; // 将本节点下的参数与父参数合并 - Map arguments = !hasArgument ? parentArguments : mergeArguments( - MiscUtils.castToMap(argument, false), + Map arguments = hasArgument ? mergeArguments( + (Map) argument, parentArguments - ); + ) : parentArguments; + // 获取处理后的template + for (ArgumentString templateId : templateIds) { + // 如果模板id被用了参数,则应先应用参数后再查询模板 + Object actualTemplate = templateId.get(parentArguments); + if (actualTemplate == null) continue; // 忽略被null掉的模板 + Object template = Optional.ofNullable(this.templates.get(Key.of(actualTemplate.toString()))) + .orElseThrow(() -> new LocalizedResourceConfigException("warning.config.template.invalid", actualTemplate.toString())); + Object processedTemplate = processUnknownValue(template, arguments); + if (processedTemplate != null) templateList.add(processedTemplate); + } + + // 获取overrides Object override = input.get(OVERRIDES); - if (override instanceof Map rawOverrides) { - // 对overrides参数应用 本节点 + 父节点 参数 - Map overrides = new LinkedHashMap<>(); - processMap(MiscUtils.castToMap(rawOverrides, false), arguments, (obj) -> { - // 如果overrides的下一级就是一个模板,则模板必须为map类型 - if (obj instanceof Map mapResult) { - overrides.putAll(MiscUtils.castToMap(mapResult, false)); - } else { - throw new IllegalArgumentException("Invalid template used. Input: " + GsonHelper.get().toJson(input) + ". Template: " + GsonHelper.get().toJson(obj)); - } - }); - // overrides是map了,merges也只能是map - if (input.get(MERGES) instanceof Map rawMerges) { - Map merges = new LinkedHashMap<>(); - processMap(MiscUtils.castToMap(rawMerges, false), arguments, (obj) -> { - // 如果merges的下一级就是一个模板,则模板必须为map类型 - if (obj instanceof Map mapResult) { - merges.putAll(MiscUtils.castToMap(mapResult, false)); - } else { - throw new IllegalArgumentException("Invalid template used. Input: " + GsonHelper.get().toJson(input) + ". Template: " + GsonHelper.get().toJson(obj)); - } - }); - // 已有template,merges,overrides 和可选的arguments - if (input.size() > (hasArgument ? 4 : 3)) { - // 会不会有一种可能,有笨比用户把模板和普通配置混合在了一起?再次遍历input后处理 - for (Map.Entry inputEntry : input.entrySet()) { - String inputKey = inputEntry.getKey(); - if (NON_TEMPLATE_KEY.contains(inputKey)) continue; - processUnknownTypeMember(inputEntry.getValue(), arguments, (processed) -> merges.put(inputKey, processed)); - } - } - // 返回处理结果 - return new TemplateProcessingResult( - templateList, - overrides, - merges, - arguments - ); - } else { - // 已有template,overrides 和可选的arguments - if (input.size() > (hasArgument ? 3 : 2)) { - Map merges = new LinkedHashMap<>(); - // 会不会有一种可能,有笨比用户把模板和普通配置混合在了一起?再次遍历input后处理 - for (Map.Entry inputEntry : input.entrySet()) { - String inputKey = inputEntry.getKey(); - if (NON_TEMPLATE_KEY.contains(inputKey)) continue; - processUnknownTypeMember(inputEntry.getValue(), arguments, (processed) -> merges.put(inputKey, processed)); - } - return new TemplateProcessingResult( - templateList, - overrides, - merges, - arguments - ); - } else { - return new TemplateProcessingResult( - templateList, - overrides, - null, - arguments - ); + boolean hasOverrides = override != null; + if (hasOverrides) { + knownKeys++; + override = processUnknownValue(override, arguments); + } + + // 获取merges + Object merge = input.get(MERGES); + boolean hasMerges = merge != null; + if (hasMerges) { + knownKeys++; + merge = processUnknownValue(merge, arguments); + } + + // 有其他意外参数 + if (input.size() > knownKeys) { + Map merges = new LinkedHashMap<>(); + // 会不会有一种可能,有笨比用户把模板和普通配置混合在了一起?再次遍历input后处理。 + for (Map.Entry inputEntry : input.entrySet()) { + ArgumentString inputKey = inputEntry.getKey(); + if (NON_TEMPLATE_ARGUMENTS.contains(inputKey)) continue; + Object key = inputKey.get(parentArguments); + if (key != null) { + merges.put(key.toString(), processUnknownValue(inputEntry.getValue(), arguments)); } } - } else if (override instanceof List overrides) { - // overrides不为空,且不是map - List processedOverrides = new ArrayList<>(overrides.size()); - for (Object item : overrides) { - processUnknownTypeMember(item, arguments, processedOverrides::add); - } - if (input.get(MERGES) instanceof List rawMerges) { - List merges = new ArrayList<>(rawMerges.size()); - for (Object item : rawMerges) { - processUnknownTypeMember(item, arguments, merges::add); + if (hasMerges && merge instanceof Map rawMerges) { + Map mergeMap = (Map) rawMerges; + for (Map.Entry inputEntry : mergeMap.entrySet()) { + ArgumentString inputKey = inputEntry.getKey(); + Object key = inputKey.get(parentArguments); + if (key != null) { + merges.put(key.toString(), processUnknownValue(inputEntry.getValue(), arguments)); + } } - return new TemplateProcessingResult( - templateList, - processedOverrides, - merges, - arguments - ); - } else { - return new TemplateProcessingResult( - templateList, - processedOverrides, - null, - arguments - ); } - } else if (override instanceof String rawOverride) { - return new TemplateProcessingResult( - templateList, - applyArgument(rawOverride, arguments), - null, - arguments - ); - } else if (override != null) { - // overrides不为空,且不是map,list。此情况不用再考虑merge了 return new TemplateProcessingResult( templateList, override, - null, + merges, arguments ); } else { - // 获取merges - Object merge = input.get(MERGES); - if (merge instanceof Map rawMerges) { - Map merges = new LinkedHashMap<>(); - processMap(MiscUtils.castToMap(rawMerges, false), arguments, (obj) -> { - // 如果merges的下一级就是一个模板,则模板必须为map类型 - if (obj instanceof Map mapResult) { - merges.putAll(MiscUtils.castToMap(mapResult, false)); - } else { - throw new IllegalArgumentException("Invalid template used. Input: " + GsonHelper.get().toJson(input) + ". Template: " + GsonHelper.get().toJson(obj)); - } - }); - // 已有template和merges 和可选的arguments - if (input.size() > (hasArgument ? 3 : 2)) { - // 会不会有一种可能,有笨比用户把模板和普通配置混合在了一起?再次遍历input后处理 - for (Map.Entry inputEntry : input.entrySet()) { - String inputKey = inputEntry.getKey(); - if (NON_TEMPLATE_KEY.contains(inputKey)) continue; - processUnknownTypeMember(inputEntry.getValue(), arguments, (processed) -> merges.put(inputKey, processed)); - } - } - return new TemplateProcessingResult( - templateList, - null, - merges, - arguments - ); - } else if (merge instanceof List rawMerges) { - List merges = new ArrayList<>(rawMerges.size()); - for (Object item : rawMerges) { - processUnknownTypeMember(item, arguments, merges::add); - } - return new TemplateProcessingResult( - templateList, - null, - merges, - arguments - ); - } else if (merge instanceof String rawMerge) { - // merge是个string - return new TemplateProcessingResult( - templateList, - null, - applyArgument(rawMerge, arguments), - arguments - ); - } else if (merge != null) { - // merge是个普通的类型 - return new TemplateProcessingResult( - templateList, - null, - merge, - arguments - ); - } else { - // 无overrides和merges - // 会不会有一种可能,有笨比用户不会使用merges,把模板和普通配置混合在了一起?再次遍历input后处理 - if (input.size() > (hasArgument ? 2 : 1)) { - Map merges = new LinkedHashMap<>(); - for (Map.Entry inputEntry : input.entrySet()) { - String inputKey = inputEntry.getKey(); - if (NON_TEMPLATE_KEY.contains(inputKey)) continue; - processUnknownTypeMember(inputEntry.getValue(), arguments, (processed) -> merges.put(inputKey, processed)); - } - return new TemplateProcessingResult( - templateList, - null, - merges, - arguments - ); - } else { - return new TemplateProcessingResult( - templateList, - null, - null, - arguments - ); - } - } + return new TemplateProcessingResult( + templateList, + override, + merge, + arguments + ); } } // 合并参数 - private Map mergeArguments(@NotNull Map rawChildArguments, + @SuppressWarnings("unchecked") + private Map mergeArguments(@NotNull Map childArguments, @NotNull Map parentArguments) { - Map result = new HashMap<>(parentArguments); - // 我们遍历一下当前节点下的所有参数,这些参数可能含有内嵌参数。所以需要对参数map先处理一次后再合并 - // arguments: - // argument_1: "{parent_argument}" - for (Map.Entry argumentEntry : rawChildArguments.entrySet()) { - // 获取最终的string形式参数 - String placeholder = applyArgument(argumentEntry.getKey(), parentArguments).toString(); + Map result = new LinkedHashMap<>(parentArguments); + for (Map.Entry argumentEntry : childArguments.entrySet()) { + Object placeholderObj = argumentEntry.getKey().get(result); + if (placeholderObj == null) continue; + String placeholder = placeholderObj.toString(); // 父亲参数最大 if (result.containsKey(placeholder)) continue; - Object rawArgument = argumentEntry.getValue(); - if (rawArgument instanceof Map mapArgument) { - // 此参数是一个map,那么对map应用模板,然后再根据map是否含有type等参数,判别其是否为带名特殊参数 - Map nestedResult = new LinkedHashMap<>(); - processMap(MiscUtils.castToMap(mapArgument, false), parentArguments, (obj) -> { - // 如果有人往arguments下塞了一个模板,则模板类型应为map - if (obj instanceof Map mapResult) { - nestedResult.putAll(MiscUtils.castToMap(mapResult, false)); - } else { - throw new IllegalArgumentException("Invalid template used. Input: " + GsonHelper.get().toJson(mapArgument) + ". Template: " + GsonHelper.get().toJson(obj)); - } - }); - result.put(placeholder, TemplateArguments.fromMap(nestedResult)); - } else if (rawArgument instanceof List listArgument) { - // 此参数是一个list,那么只需要应用模板即可 - List nestedResult = new ArrayList<>(); - for (Object item : listArgument) { - processUnknownTypeMember(item, parentArguments, nestedResult::add); - } - result.put(placeholder, new ListTemplateArgument(nestedResult)); - } else if (rawArgument == null) { - // 使用 null 覆写其父参数内容 - result.put(placeholder, NullTemplateArgument.INSTANCE); - } else if (rawArgument instanceof Number number) { - result.put(placeholder, new ObjectTemplateArgument(number)); - } else if (rawArgument instanceof Boolean booleanValue) { - result.put(placeholder, new ObjectTemplateArgument(booleanValue)); - } else { - // 将参数字符串化后,应用参数再放入 - Object applied = applyArgument(rawArgument.toString(), parentArguments); - result.put(placeholder, new ObjectTemplateArgument(applied)); + Object processedPlaceholderValue = processUnknownValue(argumentEntry.getValue(), result); + switch (processedPlaceholderValue) { + case Map map -> result.put(placeholder, TemplateArguments.fromMap(MiscUtils.castToMap(map, false))); + case List listArgument -> result.put(placeholder, new ListTemplateArgument((List) listArgument)); + case null -> result.put(placeholder, NullTemplateArgument.INSTANCE); + default -> result.put(placeholder, new ObjectTemplateArgument(processedPlaceholderValue)); } } return result; } - // 将某个输入变成最终的结果,可以是string->string,也可以是string->map/list - private Object applyArgument(String input, Map arguments) { - // 如果字符串长度连3都没有,那么肯定没有{}啊 - if (input.length() < 3) return input; - if (input.charAt(0) == '{' && input.charAt(input.length() - 1) == '}') { - String key = input.substring(1, input.length() - 1); - TemplateArgument argument = arguments.get(key); - if (argument != null) { - return argument.get(); - } - } - return replacePlaceholders(input, arguments); - } - private record TemplateProcessingResult( List templates, Object overrides, @@ -488,86 +353,4 @@ public class TemplateManagerImpl implements TemplateManager { } } } - - public static String replacePlaceholders(String input, Map replacements) { - if (input == null || input.isEmpty()) { - return input; - } - StringBuilder finalResult = new StringBuilder(); - int n = input.length(); - int lastAppendPosition = 0; // 追踪上一次追加操作结束的位置 - int i = 0; - while (i < n) { - // 检查当前字符是否为未转义的 '{' - int backslashes = 0; - int temp_i = i - 1; - while (temp_i >= 0 && input.charAt(temp_i) == '\\') { - backslashes++; - temp_i--; - } - if (input.charAt(i) == '{' && backslashes % 2 == 0) { - // 发现占位符起点 - int placeholderStartIndex = i; - // 追加从上一个位置到当前占位符之前的文本 - finalResult.append(input, lastAppendPosition, placeholderStartIndex); - // --- 开始解析占位符内部 --- - StringBuilder keyBuilder = new StringBuilder(); - int depth = 1; - int j = i + 1; - boolean foundMatch = false; - while (j < n) { - char c = input.charAt(j); - if (c == '\\') { // 处理转义 - if (j + 1 < n) { - keyBuilder.append(input.charAt(j + 1)); - j += 2; - } else { - keyBuilder.append(c); - j++; - } - } else if (c == '{') { - depth++; - keyBuilder.append(c); - j++; - } else if (c == '}') { - depth--; - if (depth == 0) { // 找到匹配的结束括号 - String key = keyBuilder.toString(); - TemplateArgument value = replacements.get(key); - if (value != null) { - // 如果在 Map 中找到值,则进行替换 - finalResult.append(value.get()); - } else { - // 否则,保留原始占位符(包括 '{}') - finalResult.append(input, placeholderStartIndex, j + 1); - } - // 更新位置指针 - i = j + 1; - lastAppendPosition = i; - foundMatch = true; - break; - } - keyBuilder.append(c); // 嵌套的 '}' - j++; - } else { - keyBuilder.append(c); - j++; - } - } - // --- 占位符解析结束 --- - if (!foundMatch) { - // 如果内层循环结束仍未找到匹配的 '}',则不进行任何特殊处理 - // 外层循环的 i 会自然递增 - i++; - } - } else { - i++; - } - } - // 追加最后一个占位符之后的所有剩余文本 - if (lastAppendPosition < n) { - finalResult.append(input, lastAppendPosition, n); - } - return finalResult.toString(); - } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/MiscUtils.java b/core/src/main/java/net/momirealms/craftengine/core/util/MiscUtils.java index d3c9b178b..bf59ac48f 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/util/MiscUtils.java +++ b/core/src/main/java/net/momirealms/craftengine/core/util/MiscUtils.java @@ -50,6 +50,22 @@ public class MiscUtils { return list; } + @SuppressWarnings("unchecked") + public static List getAsList(Object o, Class clazz) { + if (o instanceof List list) { + if (list.isEmpty()) { + return List.of(); + } + if (clazz.isInstance(list.getFirst())) { + return (List) list; + } + } + if (clazz.isInstance(o)) { + return List.of((T) o); + } + return List.of(); + } + public static Vector3f getAsVector3f(Object o, String option) { if (o == null) return new Vector3f(); if (o instanceof List list && list.size() == 3) { diff --git a/gradle.properties b/gradle.properties index 9952619bd..747826baf 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,7 +4,7 @@ org.gradle.jvmargs=-Xmx1G # Rule: [major update].[feature update].[bug fix] project_version=0.0.56.1 config_version=34 -lang_version=14 +lang_version=15 project_group=net.momirealms latest_supported_version=1.21.5 From 8189760a3ec50a18a465df9eb730507bfea80eee Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Sat, 7 Jun 2025 04:40:04 +0800 Subject: [PATCH 2/3] =?UTF-8?q?translation=E8=BF=98=E6=98=AF=E5=A4=AA?= =?UTF-8?q?=E6=85=A2=E4=BA=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/font/AbstractFontManager.java | 2 +- .../config/TranslationConfigConstructor.java | 41 +++++ .../plugin/locale/MiniMessageTranslator.java | 6 +- .../locale/MiniMessageTranslatorImpl.java | 31 +--- .../plugin/locale/TranslationManagerImpl.java | 166 +++++++++--------- 5 files changed, 139 insertions(+), 107 deletions(-) create mode 100644 core/src/main/java/net/momirealms/craftengine/core/plugin/config/TranslationConfigConstructor.java diff --git a/core/src/main/java/net/momirealms/craftengine/core/font/AbstractFontManager.java b/core/src/main/java/net/momirealms/craftengine/core/font/AbstractFontManager.java index 6fd850ca0..cce4c9e69 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/font/AbstractFontManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/font/AbstractFontManager.java @@ -286,7 +286,7 @@ public abstract class AbstractFontManager implements FontManager { } for (int i = -256; i <= 256; i++) { String shiftTag = ""; - this.tagMapper.put(shiftTag, AdventureHelper.miniMessage().deserialize(this.offsetFont.createOffset(i, FormatUtils::miniMessageFont))); + this.tagMapper.put(shiftTag, this.offsetFont.createOffset(i)); this.tagMapper.put("\\" + shiftTag, Component.text(shiftTag)); } this.imageTagTrie = Trie.builder() diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/TranslationConfigConstructor.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/TranslationConfigConstructor.java new file mode 100644 index 000000000..d44abd0af --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/TranslationConfigConstructor.java @@ -0,0 +1,41 @@ +package net.momirealms.craftengine.core.plugin.config; + +import org.yaml.snakeyaml.LoaderOptions; +import org.yaml.snakeyaml.constructor.SafeConstructor; +import org.yaml.snakeyaml.nodes.MappingNode; +import org.yaml.snakeyaml.nodes.Node; +import org.yaml.snakeyaml.nodes.NodeTuple; +import org.yaml.snakeyaml.nodes.ScalarNode; + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.StringJoiner; + +public class TranslationConfigConstructor extends SafeConstructor { + + public TranslationConfigConstructor(LoaderOptions loaderOptions) { + super(loaderOptions); + } + + @Override + protected Map constructMapping(MappingNode node) { + Map map = new LinkedHashMap<>(); + for (NodeTuple tuple : node.getValue()) { + Node keyNode = tuple.getKeyNode(); + Node valueNode = tuple.getValueNode(); + String key = constructScalar((ScalarNode) keyNode); + Object value = constructObject(valueNode); + if (value instanceof List list) { + StringJoiner stringJoiner = new StringJoiner(""); + for (Object str : list) { + stringJoiner.add(String.valueOf(str)); + } + map.put(key, stringJoiner.toString()); + } else { + map.put(key, value.toString()); + } + } + return map; + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/locale/MiniMessageTranslator.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/locale/MiniMessageTranslator.java index de6febf2f..decab7f42 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/locale/MiniMessageTranslator.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/locale/MiniMessageTranslator.java @@ -21,9 +21,5 @@ public interface MiniMessageTranslator extends Translator, Examinable { return renderer().render(component, locale); } - @NotNull Iterable sources(); - - boolean addSource(final @NotNull Translator source); - - boolean removeSource(final @NotNull Translator source); + boolean setSource(final @NotNull Translator source); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/locale/MiniMessageTranslatorImpl.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/locale/MiniMessageTranslatorImpl.java index d096d47ca..d43db429f 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/locale/MiniMessageTranslatorImpl.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/locale/MiniMessageTranslatorImpl.java @@ -20,8 +20,8 @@ import java.util.stream.Stream; public class MiniMessageTranslatorImpl implements MiniMessageTranslator { private static final Key NAME = Key.key(net.momirealms.craftengine.core.util.Key.DEFAULT_NAMESPACE, "main"); static final MiniMessageTranslatorImpl INSTANCE = new MiniMessageTranslatorImpl(); - final TranslatableComponentRenderer renderer = TranslatableComponentRenderer.usingTranslationSource(this); - private final Set sources = Collections.newSetFromMap(new ConcurrentHashMap<>()); + protected final TranslatableComponentRenderer renderer = TranslatableComponentRenderer.usingTranslationSource(this); + private Translator source; @Override public @NotNull Key name() { @@ -30,7 +30,7 @@ public class MiniMessageTranslatorImpl implements MiniMessageTranslator { @Override public @NotNull TriState hasAnyTranslations() { - if (!this.sources.isEmpty()) { + if (this.source != null) { return TriState.TRUE; } return TriState.FALSE; @@ -44,33 +44,20 @@ public class MiniMessageTranslatorImpl implements MiniMessageTranslator { @Override public @Nullable Component translate(@NotNull TranslatableComponent component, @NotNull Locale locale) { - for (final Translator source : this.sources) { - final Component translation = source.translate(component, locale); - if (translation != null) { - return translation; - } + if (this.source != null) { + return this.source.translate(component, locale); } return null; } @Override - public @NotNull Iterable sources() { - return Collections.unmodifiableSet(this.sources); - } - - @Override - public boolean addSource(final @NotNull Translator source) { - if (source == this) throw new IllegalArgumentException("MiniMessageTranslationSource"); - return this.sources.add(source); - } - - @Override - public boolean removeSource(final @NotNull Translator source) { - return this.sources.remove(source); + public boolean setSource(@NotNull Translator source) { + this.source = source; + return true; } @Override public @NotNull Stream examinableProperties() { - return Stream.of(ExaminableProperty.of("sources", this.sources)); + return Stream.of(ExaminableProperty.of("source", this.source)); } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/locale/TranslationManagerImpl.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/locale/TranslationManagerImpl.java index d73c40551..b4308602a 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/locale/TranslationManagerImpl.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/locale/TranslationManagerImpl.java @@ -9,26 +9,28 @@ import net.momirealms.craftengine.core.plugin.Plugin; import net.momirealms.craftengine.core.plugin.PluginProperties; import net.momirealms.craftengine.core.plugin.config.ConfigParser; import net.momirealms.craftengine.core.plugin.config.StringKeyConstructor; +import net.momirealms.craftengine.core.plugin.config.TranslationConfigConstructor; import net.momirealms.craftengine.core.plugin.text.minimessage.IndexedArgumentTag; import net.momirealms.craftengine.core.util.AdventureHelper; -import net.momirealms.craftengine.core.util.Pair; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.yaml.snakeyaml.DumperOptions; import org.yaml.snakeyaml.LoaderOptions; import org.yaml.snakeyaml.Yaml; import org.yaml.snakeyaml.representer.Representer; -import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; +import java.nio.file.FileVisitResult; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; -import java.util.stream.Stream; public class TranslationManagerImpl implements TranslationManager { private static final Locale DEFAULT_LOCALE = Locale.ENGLISH; @@ -38,13 +40,14 @@ public class TranslationManagerImpl implements TranslationManager { private final Path translationsDirectory; private final String langVersion; private final String[] supportedLanguages; - private final Map translationFallback = new LinkedHashMap<>(); + private final Map translationFallback = new LinkedHashMap<>(); private Locale forcedLocale = null; private Locale selectedLocale = DEFAULT_LOCALE; private MiniMessageTranslationRegistry registry; private final Map clientLangData = new HashMap<>(); private final LangParser langParser; private final I18NParser i18nParser; + private Map cachedTranslations = Map.of(); public TranslationManagerImpl(Plugin plugin) { instance = this; @@ -54,7 +57,7 @@ public class TranslationManagerImpl implements TranslationManager { this.supportedLanguages = PluginProperties.getValue("supported-languages").split(","); this.langParser = new LangParser(); this.i18nParser = new I18NParser(); - Yaml yaml = new Yaml(new StringKeyConstructor(new LoaderOptions())); + Yaml yaml = new Yaml(new TranslationConfigConstructor(new LoaderOptions())); try (InputStream is = plugin.resourceStream("translations/en.yml")) { this.translationFallback.putAll(yaml.load(is)); } catch (IOException e) { @@ -81,12 +84,7 @@ public class TranslationManagerImpl implements TranslationManager { public void reload() { // clear old data this.clientLangData.clear(); - - // remove any previous registry - if (this.registry != null) { - MiniMessageTranslator.translator().removeSource(this.registry); - this.installed.clear(); - } + this.installed.clear(); // save resources for (String lang : this.supportedLanguages) { @@ -95,8 +93,10 @@ public class TranslationManagerImpl implements TranslationManager { this.registry = MiniMessageTranslationRegistry.create(Key.key(net.momirealms.craftengine.core.util.Key.DEFAULT_NAMESPACE, "main"), AdventureHelper.miniMessage()); this.registry.defaultLocale(DEFAULT_LOCALE); - this.loadFromFileSystem(this.translationsDirectory, false); - MiniMessageTranslator.translator().addSource(this.registry); + + this.loadFromFileSystem(this.translationsDirectory); + this.loadFromCache(); + MiniMessageTranslator.translator().setSource(this.registry); this.setSelectedLocale(); } @@ -138,28 +138,19 @@ public class TranslationManagerImpl implements TranslationManager { return MiniMessageTranslator.render(component, locale); } - public void loadFromFileSystem(Path directory, boolean suppressDuplicatesError) { - List translationFiles; - try (Stream stream = Files.list(directory)) { - translationFiles = stream.filter(TranslationManagerImpl::isTranslationFile).collect(Collectors.toList()); - } catch (IOException e) { - translationFiles = Collections.emptyList(); - } - - if (translationFiles.isEmpty()) { - return; - } - + private void loadFromCache() { Map> loaded = new HashMap<>(); - for (Path translationFile : translationFiles) { - try { - Pair> result = loadTranslationFile(translationFile); - loaded.put(result.left(), result.right()); - } catch (Exception e) { - if (!suppressDuplicatesError || !isAdventureDuplicatesException(e)) { - this.plugin.logger().warn("Error loading locale file: " + translationFile.getFileName(), e); - } + + for (Map.Entry entry : this.cachedTranslations.entrySet()) { + Locale locale = TranslationManager.parseLocale(entry.getKey()); + if (locale == null) { + this.plugin.logger().warn("Unknown locale '" + entry.getKey() + "' - unable to register."); + continue; } + Map translations = entry.getValue().translations(); + this.registry.registerAll(locale, translations); + this.installed.add(locale); + loaded.put(locale, translations); } // try registering the locale without a country code - if we don't already have a registration for that @@ -175,52 +166,44 @@ public class TranslationManagerImpl implements TranslationManager { }); } - public static boolean isTranslationFile(Path path) { - return path.getFileName().toString().endsWith(".yml"); - } - - private static boolean isAdventureDuplicatesException(Exception e) { - return e instanceof IllegalArgumentException && (e.getMessage().startsWith("Invalid key") || e.getMessage().startsWith("Translation already exists")); - } - - @SuppressWarnings("unchecked") - private Pair> loadTranslationFile(Path translationFile) { - String fileName = translationFile.getFileName().toString(); - String localeString = fileName.substring(0, fileName.length() - ".yml".length()); - Locale locale = TranslationManager.parseLocale(localeString); - if (locale == null) { - throw new IllegalStateException("Unknown locale '" + localeString + "' - unable to register."); - } - - Map bundle = new HashMap<>(); - Yaml yaml = new Yaml(new StringKeyConstructor(new LoaderOptions())); - try (InputStreamReader inputStream = new InputStreamReader(new FileInputStream(translationFile.toFile()), StandardCharsets.UTF_8)) { - Map map = yaml.load(inputStream); - String langVersion = map.getOrDefault("lang-version", "").toString(); - if (!langVersion.equals(this.langVersion)) { - map = updateLangFile(map, translationFile); - } - - for (Map.Entry entry : map.entrySet()) { - if (entry.getValue() instanceof String str) { - bundle.put(entry.getKey(), str); - } else if (entry.getValue() instanceof List list) { - List strList = (List) list; - StringJoiner stringJoiner = new StringJoiner(""); - for (String str : strList) { - stringJoiner.add(str); + public void loadFromFileSystem(Path directory) { + Map previousTranslations = this.cachedTranslations; + this.cachedTranslations = new HashMap<>(); + Yaml yaml = new Yaml(new TranslationConfigConstructor(new LoaderOptions())); + try { + Files.walkFileTree(directory, new SimpleFileVisitor<>() { + @Override + public @NotNull FileVisitResult visitFile(@NotNull Path path, @NotNull BasicFileAttributes attrs) { + String fileName = path.getFileName().toString(); + if (Files.isRegularFile(path) && fileName.endsWith(".yml")) { + String localeName = fileName.substring(0, fileName.length() - ".yml".length()); + CachedTranslation cachedFile = previousTranslations.get(localeName); + long lastModifiedTime = attrs.lastModifiedTime().toMillis(); + long size = attrs.size(); + if (cachedFile != null && cachedFile.lastModified() == lastModifiedTime && cachedFile.size() == size) { + TranslationManagerImpl.this.cachedTranslations.put(localeName, cachedFile); + } else { + try (InputStreamReader inputStream = new InputStreamReader(Files.newInputStream(path), StandardCharsets.UTF_8)) { + Map data = yaml.load(inputStream); + if (data == null) return FileVisitResult.CONTINUE; + String langVersion = data.getOrDefault("lang-version", ""); + if (!langVersion.equals(TranslationManagerImpl.this.langVersion)) { + data = updateLangFile(data, path); + } + cachedFile = new CachedTranslation(data, lastModifiedTime, size); + TranslationManagerImpl.this.cachedTranslations.put(localeName, cachedFile); + } catch (IOException e) { + TranslationManagerImpl.this.plugin.logger().severe("Error while reading translation file: " + path, e); + return FileVisitResult.CONTINUE; + } + } } - bundle.put(entry.getKey(), stringJoiner.toString()); + return FileVisitResult.CONTINUE; } - } - - this.registry.registerAll(locale, bundle); - this.installed.add(locale); + }); } catch (IOException e) { - this.plugin.logger().warn(translationFile, "Error loading translation file", e); + this.plugin.logger().warn("Failed to load translation file from folder", e); } - - return Pair.of(locale, bundle); } @Override @@ -230,7 +213,7 @@ public class TranslationManagerImpl implements TranslationManager { this.plugin.senderFactory().console().sendMessage(AdventureHelper.miniMessage().deserialize(translation, new IndexedArgumentTag(Arrays.stream(args).map(Component::text).toList()))); } - private Map updateLangFile(Map previous, Path translationFile) throws IOException { + private Map updateLangFile(Map previous, Path translationFile) throws IOException { DumperOptions options = new DumperOptions(); options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); options.setPrettyFlow(true); @@ -238,11 +221,12 @@ public class TranslationManagerImpl implements TranslationManager { options.setSplitLines(false); options.setDefaultScalarStyle(DumperOptions.ScalarStyle.DOUBLE_QUOTED); Yaml yaml = new Yaml(new StringKeyConstructor(new LoaderOptions()), new Representer(options), options); - LinkedHashMap newFileContents = new LinkedHashMap<>(); - try (InputStream is = plugin.resourceStream("translations/" + translationFile.getFileName())) { - Map newMap = yaml.load(is); + LinkedHashMap newFileContents = new LinkedHashMap<>(); + try (InputStream is = this.plugin.resourceStream("translations/" + translationFile.getFileName())) { + Map newMap = yaml.load(is); newFileContents.putAll(this.translationFallback); newFileContents.putAll(newMap); + // 思考是否值得特殊处理list类型的dump?似乎并没有这个必要。用户很少会使用list类型,且dump后只改变YAML结构而不影响游戏内效果。 newFileContents.putAll(previous); newFileContents.put("lang-version", this.langVersion); String yamlString = yaml.dump(newFileContents); @@ -332,4 +316,28 @@ public class TranslationManagerImpl implements TranslationManager { TranslationManagerImpl.this.addClientTranslation(langId, sectionData); } } + + private static class CachedTranslation { + private final Map translations; + private final long lastModified; + private final long size; + + public CachedTranslation(Map translations, long lastModified, long size) { + this.lastModified = lastModified; + this.translations = translations; + this.size = size; + } + + public long lastModified() { + return lastModified; + } + + public long size() { + return size; + } + + public Map translations() { + return translations; + } + } } From c5d471ca0ac921a6c716c7eab8d1bdf392fbba22 Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Sat, 7 Jun 2025 05:10:51 +0800 Subject: [PATCH 3/3] =?UTF-8?q?config=E8=BF=98=E6=98=AF=E5=A4=AA=E6=85=A2?= =?UTF-8?q?=E4=BA=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../feature/DisableResourceCommand.java | 2 +- .../feature/EnableResourceCommand.java | 2 +- .../core/pack/AbstractPackManager.java | 6 +- .../core/plugin/config/Config.java | 108 ++++++++++-------- .../locale/MiniMessageTranslatorImpl.java | 3 - .../plugin/locale/TranslationManagerImpl.java | 36 +----- 6 files changed, 70 insertions(+), 87 deletions(-) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/DisableResourceCommand.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/DisableResourceCommand.java index c92513366..0aa938cb9 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/DisableResourceCommand.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/DisableResourceCommand.java @@ -53,7 +53,7 @@ public class DisableResourceCommand extends BukkitCommandFeature return; } } - YamlDocument document = plugin().config().loadYamlData(packMetaPath.toFile()); + YamlDocument document = plugin().config().loadYamlData(packMetaPath); document.set("enable", false); try { document.save(packMetaPath.toFile()); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/EnableResourceCommand.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/EnableResourceCommand.java index e565f9935..58960b6bc 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/EnableResourceCommand.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/EnableResourceCommand.java @@ -52,7 +52,7 @@ public class EnableResourceCommand extends BukkitCommandFeature { return; } } - YamlDocument document = plugin().config().loadYamlData(packMetaPath.toFile()); + YamlDocument document = plugin().config().loadYamlData(packMetaPath); document.set("enable", true); try { document.save(packMetaPath.toFile()); diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java b/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java index bbfe9f75f..d9befc7a4 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java @@ -266,7 +266,7 @@ public abstract class AbstractPackManager implements PackManager { String author = null; boolean enable = true; if (Files.exists(metaFile) && Files.isRegularFile(metaFile)) { - YamlDocument metaYML = Config.instance().loadYamlData(metaFile.toFile()); + YamlDocument metaYML = Config.instance().loadYamlData(metaFile); enable = metaYML.getBoolean("enable", true); namespace = metaYML.getString("namespace", namespace); description = metaYML.getString("description"); @@ -837,8 +837,8 @@ public abstract class AbstractPackManager implements PackManager { } private void generateBlockOverrides(Path generatedPackPath) { - File blockStatesFile = new File(plugin.dataFolderFile(), "blockstates.yml"); - if (!blockStatesFile.exists()) plugin.saveResource("blockstates.yml"); + Path blockStatesFile = this.plugin.dataFolderPath().resolve("blockstates.yml"); + if (!Files.exists(blockStatesFile)) this.plugin.saveResource("blockstates.yml"); YamlDocument preset = Config.instance().loadYamlData(blockStatesFile); for (Map.Entry> entry : plugin.blockManager().blockOverrides().entrySet()) { Key key = entry.getKey(); diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/Config.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/Config.java index af49ec1b7..4eaecf1f9 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/Config.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/Config.java @@ -25,12 +25,10 @@ import net.momirealms.craftengine.core.util.MiscUtils; import net.momirealms.craftengine.core.world.InjectionTarget; import net.momirealms.craftengine.core.world.chunk.storage.CompressionMethod; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; +import java.io.*; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.attribute.BasicFileAttributes; import java.util.*; import java.util.stream.Collectors; @@ -40,6 +38,8 @@ public class Config { private final Path configFilePath; private final String configVersion; private YamlDocument config; + private long lastModified; + private long size; protected boolean firstTime = true; protected boolean debug; @@ -152,49 +152,63 @@ public class Config { } public void load() { - if (Files.exists(this.configFilePath)) { - this.config = this.loadYamlData(this.configFilePath.toFile()); - String configVersion = config.getString("config-version"); - if (configVersion.equals(this.configVersion)) { - loadSettings(); - return; - } + // 文件不存在,则保存 + if (!Files.exists(this.configFilePath)) { + this.plugin.saveResource("config.yml"); + } + try { + BasicFileAttributes attributes = Files.readAttributes(this.configFilePath, BasicFileAttributes.class); + long lastModified = attributes.lastModifiedTime().toMillis(); + long size = attributes.size(); + if (lastModified != this.lastModified || size != this.size) { + byte[] configFileBytes = Files.readAllBytes(this.configFilePath); + try (InputStream inputStream = new ByteArrayInputStream(configFileBytes)) { + this.config = YamlDocument.create(inputStream); + String configVersion = this.config.getString("config-version"); + if (!configVersion.equals(this.configVersion)) { + this.updateConfigVersion(configFileBytes); + } + } + // 加载配置文件 + this.loadSettings(); + this.lastModified = lastModified; + this.size = size; + } + } catch (IOException e) { + this.plugin.logger().severe("Failed to load config.yml", e); } - this.updateConfigVersion(); - loadSettings(); } - private void updateConfigVersion() { - this.config = this.loadYamlConfig( - "config.yml", - GeneralSettings.builder() - .setRouteSeparator('.') - .setUseDefaults(false) - .build(), - LoaderSettings - .builder() - .setAutoUpdate(true) - .build(), - DumperSettings.builder() - .setEscapeUnprintable(false) - .setScalarFormatter((tag, value, role, def) -> { - if (role == NodeRole.KEY) { - return ScalarStyle.PLAIN; - } else { - return tag == Tag.STR ? ScalarStyle.DOUBLE_QUOTED : ScalarStyle.PLAIN; - } - }) - .build(), - UpdaterSettings - .builder() - .setVersioning(new BasicVersioning("config-version")) - .addIgnoredRoute(PluginProperties.getValue("config"), "resource-pack.delivery.hosting", '.') - .build() - ); + private void updateConfigVersion(byte[] bytes) throws IOException { + try (InputStream inputStream = new ByteArrayInputStream(bytes)) { + this.config = YamlDocument.create(inputStream, this.plugin.resourceStream("config.yml"), GeneralSettings.builder() + .setRouteSeparator('.') + .setUseDefaults(false) + .build(), + LoaderSettings + .builder() + .setAutoUpdate(true) + .build(), + DumperSettings.builder() + .setEscapeUnprintable(false) + .setScalarFormatter((tag, value, role, def) -> { + if (role == NodeRole.KEY) { + return ScalarStyle.PLAIN; + } else { + return tag == Tag.STR ? ScalarStyle.DOUBLE_QUOTED : ScalarStyle.PLAIN; + } + }) + .build(), + UpdaterSettings + .builder() + .setVersioning(new BasicVersioning("config-version")) + .addIgnoredRoute(PluginProperties.getValue("config"), "resource-pack.delivery.hosting", '.') + .build()); + } try { - config.save(new File(plugin.dataFolderFile(), "config.yml")); + this.config.save(new File(plugin.dataFolderFile(), "config.yml")); } catch (IOException e) { - throw new RuntimeException(e); + this.plugin.logger().warn("Could not save config.yml", e); } } @@ -714,11 +728,11 @@ public class Config { } public YamlDocument loadOrCreateYamlData(String fileName) { - File file = new File(this.plugin.dataFolderFile(), fileName); - if (!file.exists()) { + Path path = this.plugin.dataFolderPath().resolve(fileName); + if (!Files.exists(path)) { this.plugin.saveResource(fileName); } - return this.loadYamlData(file); + return this.loadYamlData(path); } public YamlDocument loadYamlConfig(String filePath, GeneralSettings generalSettings, LoaderSettings loaderSettings, DumperSettings dumperSettings, UpdaterSettings updaterSettings) { @@ -730,8 +744,8 @@ public class Config { } } - public YamlDocument loadYamlData(File file) { - try (InputStream inputStream = new FileInputStream(file)) { + public YamlDocument loadYamlData(Path file) { + try (InputStream inputStream = Files.newInputStream(file)) { return YamlDocument.create(inputStream); } catch (IOException e) { this.plugin.logger().severe("Failed to load config " + file, e); diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/locale/MiniMessageTranslatorImpl.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/locale/MiniMessageTranslatorImpl.java index d43db429f..8bbfe0ecc 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/locale/MiniMessageTranslatorImpl.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/locale/MiniMessageTranslatorImpl.java @@ -11,10 +11,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.text.MessageFormat; -import java.util.Collections; import java.util.Locale; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Stream; public class MiniMessageTranslatorImpl implements MiniMessageTranslator { diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/locale/TranslationManagerImpl.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/locale/TranslationManagerImpl.java index b4308602a..ce70f2b48 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/locale/TranslationManagerImpl.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/locale/TranslationManagerImpl.java @@ -139,8 +139,6 @@ public class TranslationManagerImpl implements TranslationManager { } private void loadFromCache() { - Map> loaded = new HashMap<>(); - for (Map.Entry entry : this.cachedTranslations.entrySet()) { Locale locale = TranslationManager.parseLocale(entry.getKey()); if (locale == null) { @@ -150,26 +148,20 @@ public class TranslationManagerImpl implements TranslationManager { Map translations = entry.getValue().translations(); this.registry.registerAll(locale, translations); this.installed.add(locale); - loaded.put(locale, translations); - } - - // try registering the locale without a country code - if we don't already have a registration for that - loaded.forEach((locale, bundle) -> { Locale localeWithoutCountry = Locale.of(locale.getLanguage()); if (!locale.equals(localeWithoutCountry) && !localeWithoutCountry.equals(DEFAULT_LOCALE) && this.installed.add(localeWithoutCountry)) { try { - this.registry.registerAll(localeWithoutCountry, bundle); + this.registry.registerAll(localeWithoutCountry, translations); } catch (IllegalArgumentException e) { // ignore } } - }); + } } public void loadFromFileSystem(Path directory) { Map previousTranslations = this.cachedTranslations; this.cachedTranslations = new HashMap<>(); - Yaml yaml = new Yaml(new TranslationConfigConstructor(new LoaderOptions())); try { Files.walkFileTree(directory, new SimpleFileVisitor<>() { @Override @@ -184,6 +176,7 @@ public class TranslationManagerImpl implements TranslationManager { TranslationManagerImpl.this.cachedTranslations.put(localeName, cachedFile); } else { try (InputStreamReader inputStream = new InputStreamReader(Files.newInputStream(path), StandardCharsets.UTF_8)) { + Yaml yaml = new Yaml(new TranslationConfigConstructor(new LoaderOptions())); Map data = yaml.load(inputStream); if (data == null) return FileVisitResult.CONTINUE; String langVersion = data.getOrDefault("lang-version", ""); @@ -317,27 +310,6 @@ public class TranslationManagerImpl implements TranslationManager { } } - private static class CachedTranslation { - private final Map translations; - private final long lastModified; - private final long size; - - public CachedTranslation(Map translations, long lastModified, long size) { - this.lastModified = lastModified; - this.translations = translations; - this.size = size; - } - - public long lastModified() { - return lastModified; - } - - public long size() { - return size; - } - - public Map translations() { - return translations; - } + private record CachedTranslation(Map translations, long lastModified, long size) { } }