From 25323dc385c49b19b88988609bb4976acb750867 Mon Sep 17 00:00:00 2001 From: Glavo Date: Wed, 8 Oct 2025 21:09:19 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B8=85=E7=90=86=20ModTranslations=20(#4634)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hmcl/ui/versions/ModTranslations.java | 245 ++++++++++-------- 1 file changed, 135 insertions(+), 110 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModTranslations.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModTranslations.java index a3da0da79..85d133053 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModTranslations.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModTranslations.java @@ -20,13 +20,13 @@ package org.jackhuang.hmcl.ui.versions; import org.jackhuang.hmcl.mod.RemoteModRepository; import org.jackhuang.hmcl.util.Pair; import org.jackhuang.hmcl.util.StringUtils; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.io.BufferedReader; import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; import java.util.*; -import java.util.stream.Collectors; import static org.jackhuang.hmcl.util.logging.Logger.LOG; import static org.jackhuang.hmcl.util.Pair.pair; @@ -57,54 +57,164 @@ public enum ModTranslations { }; public static ModTranslations getTranslationsByRepositoryType(RemoteModRepository.Type type) { - switch (type) { - case MOD: - return MOD; - case MODPACK: - return MODPACK; - default: - return EMPTY; - } + return switch (type) { + case MOD -> MOD; + case MODPACK -> MODPACK; + default -> EMPTY; + }; } private final String resourceName; - private List mods; - private Map modIdMap; // mod id -> mod - private Map curseForgeMap; // curseforge id -> mod - private List> keywords; - private int maxKeywordLength = -1; + private volatile List mods; + private volatile Map modIdMap; // mod id -> mod + private volatile Map curseForgeMap; // curseforge id -> mod + private volatile List> keywords; + private volatile int maxKeywordLength = -1; ModTranslations(String resourceName) { this.resourceName = resourceName; } + private @NotNull List getMods() { + List mods = this.mods; + if (mods != null) + return mods; + + synchronized (this) { + mods = this.mods; + if (mods != null) + return mods; + + if (StringUtils.isBlank(resourceName)) { + return this.mods = List.of(); + } + + //noinspection DataFlowIssue + try (BufferedReader reader = new BufferedReader( + new InputStreamReader( + ModTranslations.class.getResourceAsStream(resourceName), StandardCharsets.UTF_8))) { + return this.mods = reader.lines().filter(line -> !line.startsWith("#")).map(Mod::new).toList(); + } catch (Exception e) { + LOG.warning("Failed to load " + resourceName, e); + return this.mods = List.of(); + } + } + } + + private @NotNull Map getModIdMap() { + Map modIdMap = this.modIdMap; + if (modIdMap != null) + return modIdMap; + synchronized (this) { + modIdMap = this.modIdMap; + if (modIdMap != null) + return modIdMap; + + List mods = getMods(); + modIdMap = new HashMap<>(mods.size()); + for (Mod mod : mods) { + for (String id : mod.getModIds()) { + if (StringUtils.isNotBlank(id) && !"examplemod".equals(id)) { + modIdMap.put(id, mod); + } + } + } + + return this.modIdMap = modIdMap; + } + } + + private @NotNull Map getCurseForgeMap() { + Map curseForgeMap = this.curseForgeMap; + if (curseForgeMap != null) + return curseForgeMap; + + synchronized (this) { + curseForgeMap = this.curseForgeMap; + if (curseForgeMap != null) + return curseForgeMap; + + List mods = getMods(); + curseForgeMap = new HashMap<>(mods.size()); + for (Mod mod : mods) { + if (StringUtils.isNotBlank(mod.getCurseforge())) { + curseForgeMap.put(mod.getCurseforge(), mod); + } + } + + return this.curseForgeMap = curseForgeMap; + } + } + + private @NotNull List> getKeywords() { + List> keywords = this.keywords; + if (keywords != null) + return keywords; + + synchronized (this) { + keywords = this.keywords; + if (keywords != null) + return keywords; + + List mods = getMods(); + + keywords = new ArrayList<>(); + int maxKeywordLength = -1; + for (Mod mod : mods) { + if (StringUtils.isNotBlank(mod.getName())) { + keywords.add(pair(mod.getName(), mod)); + maxKeywordLength = Math.max(maxKeywordLength, mod.getName().length()); + } + if (StringUtils.isNotBlank(mod.getSubname())) { + keywords.add(pair(mod.getSubname(), mod)); + maxKeywordLength = Math.max(maxKeywordLength, mod.getSubname().length()); + } + if (StringUtils.isNotBlank(mod.getAbbr())) { + keywords.add(pair(mod.getAbbr(), mod)); + maxKeywordLength = Math.max(maxKeywordLength, mod.getAbbr().length()); + } + } + + this.maxKeywordLength = maxKeywordLength; + return this.keywords = keywords; + } + } + + private int getMaxKeywordLength() { + int maxKeywordLength = this.maxKeywordLength; + if (maxKeywordLength >= 0) + return maxKeywordLength; + + // Ensure maxKeywordLength is initialized + getKeywords(); + return this.maxKeywordLength; + } + @Nullable public Mod getModByCurseForgeId(String id) { - if (StringUtils.isBlank(id) || !loadCurseForgeMap()) return null; + if (StringUtils.isBlank(id)) return null; - return curseForgeMap.get(id); + return getCurseForgeMap().get(id); } @Nullable public Mod getModById(String id) { - if (StringUtils.isBlank(id) || !loadModIdMap()) return null; + if (StringUtils.isBlank(id)) return null; - return modIdMap.get(id); + return getModIdMap().get(id); } public abstract String getMcmodUrl(Mod mod); public List searchMod(String query) { - if (!loadKeywords()) return Collections.emptyList(); - - StringBuilder newQuery = ((CharSequence) query).chars() + StringBuilder newQuery = query.chars() .filter(ch -> !Character.isSpaceChar(ch)) .collect(StringBuilder::new, (sb, value) -> sb.append((char) value), StringBuilder::append); query = newQuery.toString(); - StringUtils.LongestCommonSubsequence lcs = new StringUtils.LongestCommonSubsequence(query.length(), maxKeywordLength); + StringUtils.LongestCommonSubsequence lcs = new StringUtils.LongestCommonSubsequence(query.length(), getMaxKeywordLength()); List> modList = new ArrayList<>(); - for (Pair keyword : keywords) { + for (Pair keyword : getKeywords()) { int value = lcs.calc(query, keyword.getKey()); if (value >= Math.max(1, query.length() - 3)) { modList.add(pair(value, keyword.getValue())); @@ -113,92 +223,7 @@ public enum ModTranslations { return modList.stream() .sorted((a, b) -> -a.getKey().compareTo(b.getKey())) .map(Pair::getValue) - .collect(Collectors.toList()); - } - - private boolean loadFromResource() { - if (mods != null) return true; - if (StringUtils.isBlank(resourceName)) { - mods = Collections.emptyList(); - return true; - } - - //noinspection DataFlowIssue - try (BufferedReader reader = new BufferedReader( - new InputStreamReader( - ModTranslations.class.getResourceAsStream(resourceName), StandardCharsets.UTF_8))) { - mods = reader.lines().filter(line -> !line.startsWith("#")).map(Mod::new).collect(Collectors.toList()); - return true; - } catch (Exception e) { - LOG.warning("Failed to load " + resourceName, e); - return false; - } - } - - private boolean loadCurseForgeMap() { - if (curseForgeMap != null) { - return true; - } - - if (mods == null) { - if (!loadFromResource()) return false; - } - - curseForgeMap = new HashMap<>(); - for (Mod mod : mods) { - if (StringUtils.isNotBlank(mod.getCurseforge())) { - curseForgeMap.put(mod.getCurseforge(), mod); - } - } - return true; - } - - private boolean loadModIdMap() { - if (modIdMap != null) { - return true; - } - - if (mods == null) { - if (!loadFromResource()) return false; - } - - modIdMap = new HashMap<>(); - for (Mod mod : mods) { - for (String id : mod.getModIds()) { - if (StringUtils.isNotBlank(id) && !"examplemod".equals(id)) { - modIdMap.put(id, mod); - } - } - } - return true; - } - - private boolean loadKeywords() { - if (keywords != null) { - return true; - } - - if (mods == null) { - if (!loadFromResource()) return false; - } - - keywords = new ArrayList<>(); - maxKeywordLength = -1; - for (Mod mod : mods) { - if (StringUtils.isNotBlank(mod.getName())) { - keywords.add(pair(mod.getName(), mod)); - maxKeywordLength = Math.max(maxKeywordLength, mod.getName().length()); - } - if (StringUtils.isNotBlank(mod.getSubname())) { - keywords.add(pair(mod.getSubname(), mod)); - maxKeywordLength = Math.max(maxKeywordLength, mod.getSubname().length()); - } - if (StringUtils.isNotBlank(mod.getAbbr())) { - keywords.add(pair(mod.getAbbr(), mod)); - maxKeywordLength = Math.max(maxKeywordLength, mod.getAbbr().length()); - } - } - return true; + .toList(); } public static final class Mod { @@ -217,7 +242,7 @@ public enum ModTranslations { curseforge = items[0]; mcmod = items[1]; - modIds = Collections.unmodifiableList(Arrays.asList(items[2].split(","))); + modIds = List.of(items[2].split(",")); name = items[3]; subname = items[4]; abbr = items[5];