From adfb4b11a1ab3bacdb9c840e92c4b48436737092 Mon Sep 17 00:00:00 2001 From: Aikar Date: Fri, 24 Nov 2017 13:05:20 -0500 Subject: [PATCH] Add support for Per Player Locale on Bukkit - Resolves #62 --- .../aikar/commands/BukkitCommandManager.java | 85 ++++++++++++++++++- .../aikar/commands/BungeeCommandManager.java | 4 +- .../java/co/aikar/commands/ACFPatterns.java | 1 + .../java/co/aikar/commands/BaseCommand.java | 2 +- .../co/aikar/commands/CommandManager.java | 32 ++++++- .../commands/IssuerLocaleChangedCallback.java | 30 +++++++ .../main/java/co/aikar/commands/Locales.java | 2 +- .../aikar/commands/SpongeCommandManager.java | 4 +- 8 files changed, 148 insertions(+), 12 deletions(-) create mode 100644 core/src/main/java/co/aikar/commands/IssuerLocaleChangedCallback.java diff --git a/bukkit/src/main/java/co/aikar/commands/BukkitCommandManager.java b/bukkit/src/main/java/co/aikar/commands/BukkitCommandManager.java index a4eb45e6..62504e8a 100644 --- a/bukkit/src/main/java/co/aikar/commands/BukkitCommandManager.java +++ b/bukkit/src/main/java/co/aikar/commands/BukkitCommandManager.java @@ -26,6 +26,7 @@ package co.aikar.commands; import co.aikar.commands.apachecommonslang.ApacheCommonsExceptionUtil; import co.aikar.timings.lib.MCTiming; import co.aikar.timings.lib.TimingManager; +import com.google.common.collect.Maps; import org.bukkit.Bukkit; import org.bukkit.ChatColor; import org.bukkit.Server; @@ -33,10 +34,14 @@ import org.bukkit.command.Command; import org.bukkit.command.CommandMap; import org.bukkit.command.CommandSender; import org.bukkit.command.SimpleCommandMap; +import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.event.player.PlayerQuitEvent; import org.bukkit.event.server.PluginDisableEvent; import org.bukkit.plugin.Plugin; +import org.bukkit.scheduler.BukkitTask; import org.jetbrains.annotations.NotNull; import java.lang.reflect.Field; @@ -44,23 +49,29 @@ import java.lang.reflect.Method; import java.lang.reflect.Parameter; import java.util.HashMap; import java.util.List; +import java.util.Locale; import java.util.Map; +import java.util.Objects; +import java.util.UUID; import java.util.logging.Level; import java.util.logging.Logger; @SuppressWarnings("WeakerAccess") -public class BukkitCommandManager extends CommandManager { +public class BukkitCommandManager extends CommandManager { @SuppressWarnings("WeakerAccess") protected final Plugin plugin; private final CommandMap commandMap; private final TimingManager timingManager; + private final BukkitTask localeTask; protected Map knownCommands = new HashMap<>(); protected Map registeredCommands = new HashMap<>(); protected BukkitCommandContexts contexts; protected BukkitCommandCompletions completions; MCTiming commandTiming; protected BukkitLocales locales; + private boolean cantReadLocale = false; + protected Map issuersLocale = Maps.newConcurrentMap(); @SuppressWarnings("JavaReflectionMemberAccess") public BukkitCommandManager(Plugin plugin) { @@ -74,6 +85,12 @@ public class BukkitCommandManager extends CommandManager { + if (cantReadLocale) { + return; + } + Bukkit.getOnlinePlayers().forEach(this::readPlayerLocale); + }, 5, 5); } @NotNull private CommandMap hookCommandMap() { @@ -213,6 +230,49 @@ public class BukkitCommandManager extends CommandManager 1 ? new Locale(split[0], split[1]) : new Locale(split[0]); + Locale prev = issuersLocale.put(player.getUniqueId(), locale); + if (!Objects.equals(locale, prev)) { + this.notifyLocaleChange(getCommandIssuer(player), prev, locale); + } + } + } + } catch (Exception e) { + cantReadLocale = true; + this.localeTask.cancel(); + this.log(LogLevel.INFO, "Can't read players locale, you will be unable to automatically detect players language. Only Bukkit 1.7+ is supported for this.", e); + } + } + private class ACFBukkitListener implements Listener { private final Plugin plugin; @@ -227,6 +287,17 @@ public class BukkitCommandManager extends CommandManager readPlayerLocale(player), 20); + } + + @EventHandler + public void onPlayerJoin(PlayerQuitEvent event) { + issuersLocale.remove(event.getPlayer().getUniqueId()); + } } public TimingManager getTimings() { @@ -239,7 +310,7 @@ public class BukkitCommandManager extends CommandManager { +public class BungeeCommandManager extends CommandManager { protected final Plugin plugin; protected Map registeredCommands = new HashMap<>(); @@ -131,7 +131,7 @@ public class BungeeCommandManager extends CommandManager manager = null; + CommandManager manager = null; BaseCommand parentCommand; Map registeredCommands = new HashMap<>(); String description; diff --git a/core/src/main/java/co/aikar/commands/CommandManager.java b/core/src/main/java/co/aikar/commands/CommandManager.java index e2a76c31..ad801918 100644 --- a/core/src/main/java/co/aikar/commands/CommandManager.java +++ b/core/src/main/java/co/aikar/commands/CommandManager.java @@ -24,15 +24,22 @@ package co.aikar.commands; import co.aikar.locales.MessageKeyProvider; +import com.google.common.collect.Lists; import com.google.common.collect.Sets; import org.jetbrains.annotations.NotNull; import java.lang.reflect.Method; import java.lang.reflect.Parameter; -import java.util.*; +import java.util.HashMap; +import java.util.IdentityHashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.Stack; @SuppressWarnings("WeakerAccess") -public abstract class CommandManager > { +public abstract class CommandManager > { /** * This is a stack incase a command calls a command @@ -48,11 +55,14 @@ public abstract class CommandManager > { protected Map rootCommands = new HashMap<>(); protected final CommandReplacements replacements = new CommandReplacements(this); protected ExceptionHandler defaultExceptionHandler = null; - + + + protected List> localeChangedCallbacks = Lists.newArrayList(); protected Set supportedLanguages = Sets.newHashSet(Locales.ENGLISH, Locales.GERMAN, Locales.SPANISH, Locales.CZECH); protected Map formatters = new IdentityHashMap<>(); protected F defaultFormatter; protected int defaultHelpPerPage = 10; + private Set unstableAPIs = Sets.newHashSet(); public static CommandOperationContext getCurrentCommandOperationContext() { @@ -165,7 +175,7 @@ public abstract class CommandManager > { public abstract boolean isCommandIssuer(Class type); // TODO: Change this to I if we make a breaking change - public abstract CommandIssuer getCommandIssuer(Object issuer); + public abstract AI getCommandIssuer(Object issuer); public abstract RootCommand createRootCommand(String cmd); @@ -273,6 +283,20 @@ public abstract class CommandManager > { return message; } + public void onLocaleChange(IssuerLocaleChangedCallback onChange) { + localeChangedCallbacks.add(onChange); + } + + public void notifyLocaleChange(AI issuer, Locale oldLocale, Locale newLocale) { + localeChangedCallbacks.forEach(cb -> { + try { + cb.onIssuerLocaleChange(issuer, oldLocale, newLocale); + } catch (Exception e) { + this.log(LogLevel.ERROR, "Error in notifyLocaleChange", e); + } + }); + } + public Locale getIssuerLocale(CommandIssuer issuer) { return getLocales().getDefaultLocale(); } diff --git a/core/src/main/java/co/aikar/commands/IssuerLocaleChangedCallback.java b/core/src/main/java/co/aikar/commands/IssuerLocaleChangedCallback.java new file mode 100644 index 00000000..d177c4b2 --- /dev/null +++ b/core/src/main/java/co/aikar/commands/IssuerLocaleChangedCallback.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2016-2017 Daniel Ennis (Aikar) - MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package co.aikar.commands; + +import java.util.Locale; + +public interface IssuerLocaleChangedCallback { + void onIssuerLocaleChange(I issuer, Locale oldLocale, Locale newLocale); +} diff --git a/core/src/main/java/co/aikar/commands/Locales.java b/core/src/main/java/co/aikar/commands/Locales.java index 890b3630..38cc4d54 100644 --- a/core/src/main/java/co/aikar/commands/Locales.java +++ b/core/src/main/java/co/aikar/commands/Locales.java @@ -72,7 +72,7 @@ public class Locales { public static final Locale WELSH = new Locale("cy"); - private final CommandManager manager; + private final CommandManager manager; private final LocaleManager localeManager; private final SetMultimap loadedBundles = HashMultimap.create(); diff --git a/sponge/src/main/java/co/aikar/commands/SpongeCommandManager.java b/sponge/src/main/java/co/aikar/commands/SpongeCommandManager.java index 5fcf7a42..0fca0315 100644 --- a/sponge/src/main/java/co/aikar/commands/SpongeCommandManager.java +++ b/sponge/src/main/java/co/aikar/commands/SpongeCommandManager.java @@ -40,7 +40,7 @@ import java.util.List; import java.util.Map; @SuppressWarnings("WeakerAccess") -public class SpongeCommandManager extends CommandManager { +public class SpongeCommandManager extends CommandManager { protected final PluginContainer plugin; protected Map registeredCommands = new HashMap<>(); @@ -126,7 +126,7 @@ public class SpongeCommandManager extends CommandManager