diff --git a/.gitignore b/.gitignore index 3e757f6..6136bf5 100644 --- a/.gitignore +++ b/.gitignore @@ -21,4 +21,4 @@ hs_err_pid* .gradle/4.3.1/ .idea -target +target \ No newline at end of file diff --git a/pom.xml b/pom.xml index 7653b83..c21d414 100644 --- a/pom.xml +++ b/pom.xml @@ -72,12 +72,6 @@ activejdbc 2.0 - - javax.servlet - servlet-api - 2.5 - provided - com.h2database h2 diff --git a/src/main/java/com/ilummc/tlib/inject/TConfigWatcher.java b/src/main/java/com/ilummc/tlib/inject/TConfigWatcher.java index 34d67b0..c33e1d6 100644 --- a/src/main/java/com/ilummc/tlib/inject/TConfigWatcher.java +++ b/src/main/java/com/ilummc/tlib/inject/TConfigWatcher.java @@ -19,7 +19,8 @@ import java.util.function.Consumer; */ public class TConfigWatcher { - private final ScheduledExecutorService service = Executors.newScheduledThreadPool(1, new BasicThreadFactory.Builder().namingPattern("tconfig-watcher-schedule-pool-%d").daemon(true).build()); + private final ScheduledExecutorService service = Executors.newScheduledThreadPool(1, + new BasicThreadFactory.Builder().namingPattern("TConfigWatcherService-%d").build()); private final Map>> map = new HashMap<>(); @@ -54,7 +55,16 @@ public class TConfigWatcher { public void removeListener(File file) { synchronized (map) { - map.entrySet().removeIf(entry -> entry.getValue().getLeft().equals(file)); + map.entrySet().removeIf(entry -> { + if (entry.getValue().getLeft().equals(file)) { + try { + entry.getKey().close(); + } catch (IOException ignored) { + } + return true; + } + return false; + }); } } diff --git a/src/main/java/com/ilummc/tlib/resources/TLocaleInstance.java b/src/main/java/com/ilummc/tlib/resources/TLocaleInstance.java index fe384f8..5664243 100644 --- a/src/main/java/com/ilummc/tlib/resources/TLocaleInstance.java +++ b/src/main/java/com/ilummc/tlib/resources/TLocaleInstance.java @@ -24,7 +24,6 @@ class TLocaleInstance { private final Map> map = new HashMap<>(); private final Plugin plugin; - private int updateNodes; TLocaleInstance(Plugin plugin) { this.plugin = plugin; @@ -47,10 +46,6 @@ class TLocaleInstance { return plugin; } - public int getUpdateNodes() { - return updateNodes; - } - public void sendTo(String path, CommandSender sender, String... args) { try { map.getOrDefault(path, ImmutableList.of(TLocaleSendable.getEmpty(path))).forEach(sendable -> { @@ -75,32 +70,34 @@ class TLocaleInstance { return map.getOrDefault(path, ImmutableList.of(TLocaleSendable.getEmpty(path))).get(0).asStringList(args); } - public void load(YamlConfiguration configuration) { - updateNodes = 0; - configuration.getKeys(true).forEach(s -> { - boolean isCover = false; - Object object = configuration.get(s); - if (object instanceof TLocaleSendable) { - isCover = map.put(s, Collections.singletonList((TLocaleSendable) object)) != null; - } else if (object instanceof List && !((List) object).isEmpty()) { - isCover = map.put(s, ((List) object).stream().map(TO_SENDABLE).collect(Collectors.toList())) != null; - } else if (!(object instanceof ConfigurationSection)) { - String str = String.valueOf(object); - isCover = map.put(s, Collections.singletonList(str.length() == 0 ? TLocaleSendable.getEmpty() : TLocaleText.of(str))) != null; - } - if (isCover) { - updateNodes++; - } - }); - } - private static final Function TO_SENDABLE = o -> { if (o instanceof TLocaleSendable) { return ((TLocaleSendable) o); - } else if (o instanceof String) { - return TLocaleText.of(((String) o)); + } else if (o instanceof String || (o instanceof List && isListString(((List) o)))) { + return TLocaleText.of(o); } else { return TLocaleText.of(String.valueOf(o)); } }; + + private static boolean isListString(List list) { + for (Object o : list) { + if (!(o instanceof String)) return false; + } + return true; + } + + public void load(YamlConfiguration configuration) { + configuration.getKeys(true).forEach(s -> { + Object object = configuration.get(s); + if (object instanceof TLocaleSendable) { + map.put(s, Collections.singletonList((TLocaleSendable) object)); + } else if (object instanceof List && !((List) object).isEmpty()) { + map.put(s, ((List) object).stream().map(TO_SENDABLE).collect(Collectors.toList())); + } else if (!(object instanceof ConfigurationSection)) { + String str = String.valueOf(object); + map.put(s, Collections.singletonList(str.length() == 0 ? TLocaleSendable.getEmpty() : TLocaleText.of(str))); + } + }); + } } diff --git a/src/main/java/com/ilummc/tlib/resources/TLocaleLoader.java b/src/main/java/com/ilummc/tlib/resources/TLocaleLoader.java index 84990b6..3e0f11e 100644 --- a/src/main/java/com/ilummc/tlib/resources/TLocaleLoader.java +++ b/src/main/java/com/ilummc/tlib/resources/TLocaleLoader.java @@ -1,5 +1,6 @@ package com.ilummc.tlib.resources; +import com.google.common.io.Files; import com.ilummc.tlib.TLib; import com.ilummc.tlib.annotations.TLocalePlugin; import com.ilummc.tlib.logger.TLogger; @@ -14,14 +15,14 @@ import org.bukkit.command.CommandSender; import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.configuration.serialization.ConfigurationSerialization; import org.bukkit.plugin.Plugin; +import org.yaml.snakeyaml.DumperOptions; +import org.yaml.snakeyaml.Yaml; import java.io.File; +import java.io.IOException; import java.io.InputStream; import java.nio.charset.Charset; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Optional; +import java.util.*; import java.util.concurrent.ConcurrentHashMap; public class TLocaleLoader { @@ -75,23 +76,20 @@ public class TLocaleLoader { if (isLoadLocale(plugin, isCover)) { // 获取文件 File localeFile = getLocaleFile(plugin); - if (localeFile == null) { - return; - } // 加载文件 infoLogger("TRY-LOADING-LANG", plugin.getName(), localeFile.getName()); - YamlConfiguration localeConfiguration = ConfigUtils.loadYaml(plugin, localeFile); - YamlConfiguration localeConfigurationAtStream = getLocaleAtStream(plugin, localeFile); + Map originMap = getLocaleAtStream(plugin, localeFile); + + TLib.getTLib().getConfigWatcher().removeListener(localeFile); // 载入配置 - loadPluginLocale(plugin, localeFile, localeConfiguration, localeConfigurationAtStream); + updateAndLoad(plugin, localeFile, originMap); // 注册监听 - TLib.getTLib().getConfigWatcher().removeListener(localeFile); TLib.getTLib().getConfigWatcher().addListener(localeFile, null, obj -> { infoLogger("RELOADING-LANG", plugin.getName()); - loadPluginLocale(plugin, localeFile, ConfigUtils.loadYaml(plugin, localeFile), getLocaleAtStream(plugin, localeFile)); + updateAndLoad(plugin, localeFile, getLocaleAtStream(plugin, localeFile)); }); } } catch (Exception e) { @@ -117,22 +115,22 @@ public class TLocaleLoader { private static File getLocaleFile(Plugin plugin) { releaseLocales(plugin); - return getLocalePriority().stream().map(localeName -> new File(plugin.getDataFolder(), "lang/" + localeName + ".yml")).filter(File::exists).findFirst().orElse(null); + return getLocalePriority().stream().map(localeName -> new File(plugin.getDataFolder(), "lang/" + localeName + ".yml")).filter(File::exists).findFirst().orElseThrow(NullPointerException::new); } private static void releaseLocales(Plugin plugin) { getLocalePriority().stream().filter(localeName -> !new File(plugin.getDataFolder(), "lang/" + localeName + ".yml").exists() && plugin.getResource("lang/" + localeName + ".yml") != null).forEach(localeName -> plugin.saveResource("lang/" + localeName + ".yml", true)); } - public static boolean isLocaleLoaded(Plugin plugin) { + private static boolean isLocaleLoaded(Plugin plugin) { return map.containsKey(plugin.getName()); } - public static boolean isDependWithTabooLib(Plugin plugin) { + private static boolean isDependWithTabooLib(Plugin plugin) { return plugin.getClass().getAnnotation(TLocalePlugin.class) != null || plugin.getDescription().getDepend().contains(Main.getInst().getName()) || plugin.getDescription().getSoftDepend().contains(Main.getInst().getName()); } - public static List getLocalePriority() { + private static List getLocalePriority() { return Main.getInst().getConfig().contains("LOCALE.PRIORITY") ? Main.getInst().getConfig().getStringList("LOCALE.PRIORITY") : Collections.singletonList("zh_CN"); } @@ -142,29 +140,63 @@ public class TLocaleLoader { return instance; } - private static YamlConfiguration getLocaleAtStream(Plugin plugin, File localeFile) { + private static Map getLocaleAtStream(Plugin plugin, File localeFile) { InputStream localeInputSteam = plugin.getClass().getResourceAsStream("/lang/" + localeFile.getName()); try { String yamlText = new String(IO.readFully(localeInputSteam), Charset.forName("utf-8")); - YamlConfiguration yaml = new YamlConfiguration(); - yaml.loadFromString(yamlText); - return yaml; - } catch (Exception ignored) { - return null; + Object load = new Yaml().load(yamlText); + return load instanceof Map ? (Map) load : new HashMap<>(0); + } catch (Exception e) { + return new HashMap<>(0); } } - private static void loadPluginLocale(Plugin plugin, File localeFile, YamlConfiguration localeConfiguration, YamlConfiguration localeConfigurationAtStream) { - TLocaleInstance localeInstance = getLocaleInstance(plugin); - boolean versionOutOfDate = isVersionOutOfDate(localeConfiguration, localeConfigurationAtStream); - if (versionOutOfDate) { - localeInstance.load(localeConfigurationAtStream); + private static Map currentLocaleMap(File localeFile) { + try { + Object load = new Yaml().load(Files.toString(localeFile, Charset.forName("utf-8"))); + return load instanceof Map ? (Map) load : new HashMap<>(0); + } catch (Exception e) { + return new HashMap<>(0); } + } + + private static int compareAndSet(Map origin, Map current, File file) { + int i = compareMaps(origin, current); + DumperOptions options = new DumperOptions(); + options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); + options.setAllowUnicode(false); + Yaml yaml = new Yaml(options); + String dump = yaml.dump(current); + try { + Files.write(dump.getBytes(Charset.forName("utf-8")), file); + } catch (IOException ignored) { + } + return i; + } + + @SuppressWarnings("unchecked") + private static int compareMaps(Map origin, Map current) { + int res = 0; + for (Map.Entry entry : origin.entrySet()) { + if (current.putIfAbsent(entry.getKey(), entry.getValue()) != null) { + if (entry.getValue() instanceof Map && !((Map) entry.getValue()).containsKey("==") && current.get(entry.getKey()) instanceof Map) { + res += compareMaps((Map) entry.getValue(), (Map) current.get(entry.getKey())); + } + } else ++res; + } + return res; + } + + private static void updateAndLoad(Plugin plugin, File localeFile, Map originMap) { + Map currentMap = currentLocaleMap(localeFile); + int update = compareAndSet(originMap, currentMap, localeFile); + TLocaleInstance localeInstance = getLocaleInstance(plugin); + YamlConfiguration localeConfiguration = ConfigUtils.loadYaml(plugin, localeFile); localeInstance.load(localeConfiguration); - if (!versionOutOfDate || localeInstance.size() - localeInstance.getUpdateNodes() == 0) { + if (update == 0) { infoLogger("SUCCESS-LOADING-LANG-NORMAL", plugin.getName(), localeFile.getName().split("\\.")[0], String.valueOf(localeInstance.size())); } else { - infoLogger("SUCCESS-LOADING-LANG-UPDATE", plugin.getName(), localeFile.getName().split("\\.")[0], String.valueOf(localeInstance.size()), String.valueOf(localeInstance.size() - localeInstance.getUpdateNodes())); + infoLogger("SUCCESS-LOADING-LANG-UPDATE", plugin.getName(), localeFile.getName().split("\\.")[0], String.valueOf(localeInstance.size()), String.valueOf(update)); } } } diff --git a/src/main/java/com/ilummc/tlib/resources/type/TLocaleJson.java b/src/main/java/com/ilummc/tlib/resources/type/TLocaleJson.java index f4632b9..c30aad5 100644 --- a/src/main/java/com/ilummc/tlib/resources/type/TLocaleJson.java +++ b/src/main/java/com/ilummc/tlib/resources/type/TLocaleJson.java @@ -40,28 +40,41 @@ public class TLocaleJson implements TLocaleSendable, ConfigurationSerializable { public static TLocaleJson valueOf(Map map) { boolean papi = (boolean) map.getOrDefault("papi", Main.getInst().getConfig().getBoolean("LOCALE.USE_PAPI", false)); + + // text 里面的东西 List textList = getTextList(map.getOrDefault("text", "Empty Node")); + + // 分析 args 并替换 Object argsObj = map.get("args"); if (argsObj instanceof Map) { Map section = new HashMap<>(((Map) argsObj).size()); + + // valueOf(k) 是因为这个键可能加载为一个 Integer 导致 contains(String) 返回 false ((Map) argsObj).forEach((k, v) -> section.put(String.valueOf(k), v)); List collect = textList.stream().map(s -> { int index = 0; String[] template = pattern.split(s); Matcher matcher = pattern.matcher(s); + // 有可能开头和结尾是替换文本,所以做个特判 List builder = template.length > index ? new ArrayList<>(Arrays.asList(TextComponent.fromLegacyText(template[index++]))) : new ArrayList<>(); while (matcher.find()) { String replace = matcher.group(); + // 假的 <@> if (replace.length() <= 2) { continue; } + // 真的 <@xxx> replace = replace.substring(1, replace.length() - 1); String[] split = replace.split("@"); + // @ 前面的字符串 String text = split.length > 1 ? split[0] : ""; + // @ 后面的节点名 String node = split.length > 1 ? split[1] : split[0]; + // 如果 args 有这个 xxx if (section.containsKey(node)) { Map arg = (Map) section.get(node); text = ChatColor.translateAlternateColorCodes('&', String.valueOf(arg.getOrDefault("text", text))); + // 可能有很多个 BaseComponent,于是为每个 component 单独设置各种事件 BaseComponent[] component = TextComponent.fromLegacyText(text); arg.forEach((key, value) -> { if ("suggest".equalsIgnoreCase(key)) { @@ -72,11 +85,14 @@ public class TLocaleJson implements TLocaleSendable, ConfigurationSerializable { Arrays.stream(component).forEach(baseComponent -> baseComponent.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new ComponentBuilder(ChatColor.translateAlternateColorCodes('&', String.valueOf(value))).create()))); } }); + // 添加到原来的 list 里面 builder.addAll(Arrays.asList(component)); } else { + // 这个参数节点并没有找到,于是随便放点字符串进去 builder.addAll(Arrays.asList(TextComponent.fromLegacyText(text))); TLib.getTLib().getLogger().warn(Strings.replaceWithOrder(TLib.getInternalLanguage().getString("MISSING-ARGUMENT"), node)); } + // 有可能一开头就是 <@xxx>,然后 split 出来就少了一些,于是直接加上 if (index < template.length) { builder.addAll(Arrays.asList(TextComponent.fromLegacyText(template[index++]))); } diff --git a/src/main/java/com/ilummc/tlib/resources/type/TLocaleText.java b/src/main/java/com/ilummc/tlib/resources/type/TLocaleText.java index 86db573..b23185e 100644 --- a/src/main/java/com/ilummc/tlib/resources/type/TLocaleText.java +++ b/src/main/java/com/ilummc/tlib/resources/type/TLocaleText.java @@ -63,6 +63,10 @@ public class TLocaleText implements TLocaleSendable, ConfigurationSerializable { return new TLocaleText(ChatColor.translateAlternateColorCodes('&', s), Main.getInst().getConfig().getBoolean("LOCALE.USE_PAPI", false)); } + public static TLocaleText of(Object o) { + return o instanceof String ? of(((String) o)) : new TLocaleText(o, false); + } + @Override public void sendTo(CommandSender sender, String... args) { if (text instanceof String) { diff --git a/src/main/resources/lang/zh_CN.yml b/src/main/resources/lang/zh_CN.yml index 104e0fb..955f797 100644 --- a/src/main/resources/lang/zh_CN.yml +++ b/src/main/resources/lang/zh_CN.yml @@ -462,6 +462,4 @@ COMMANDS: DATABASE: CONNECTION-ESTABLISHED: '成功连接到 {0} 数据库,连接池大小 {1}' - CONNECTION-ERROR: '连接到数据库错误:{0}' - -VERSION: 4.0 \ No newline at end of file + CONNECTION-ERROR: '连接到数据库错误:{0}' \ No newline at end of file