diff --git a/bukkit/src/main/java/co/aikar/commands/BukkitCommandIssuer.java b/bukkit/src/main/java/co/aikar/commands/BukkitCommandIssuer.java index 6af02580..5e153183 100644 --- a/bukkit/src/main/java/co/aikar/commands/BukkitCommandIssuer.java +++ b/bukkit/src/main/java/co/aikar/commands/BukkitCommandIssuer.java @@ -23,6 +23,7 @@ package co.aikar.commands; +import org.bukkit.ChatColor; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; @@ -45,8 +46,15 @@ public class BukkitCommandIssuer implements CommandIssuer { } @Override - public void sendMessage(String message) { - sender.sendMessage(ACFBukkitUtil.color(message)); + public void sendMessage(MessageType type, String message) { + switch (type) { + case ERROR: + case SYNTAX: + sender.sendMessage(ChatColor.RED + ACFBukkitUtil.color(message)); + break; + default: + sender.sendMessage(ChatColor.YELLOW + ACFBukkitUtil.color(message)); + } } @Override diff --git a/bungee/src/main/java/co/aikar/commands/BungeeCommandIssuer.java b/bungee/src/main/java/co/aikar/commands/BungeeCommandIssuer.java index 4d9dcb8d..1f7bd33f 100644 --- a/bungee/src/main/java/co/aikar/commands/BungeeCommandIssuer.java +++ b/bungee/src/main/java/co/aikar/commands/BungeeCommandIssuer.java @@ -23,6 +23,7 @@ package co.aikar.commands; +import net.md_5.bungee.api.ChatColor; import net.md_5.bungee.api.CommandSender; import net.md_5.bungee.api.chat.TextComponent; import net.md_5.bungee.api.connection.ProxiedPlayer; @@ -46,8 +47,15 @@ public class BungeeCommandIssuer implements CommandIssuer{ } @Override - public void sendMessage(String message) { - sender.sendMessage(new TextComponent(ACFBungeeUtil.color(message))); + public void sendMessage(MessageType type, String message) { + switch (type) { + case ERROR: + case SYNTAX: + sender.sendMessage(new TextComponent(ChatColor.RED + ACFBungeeUtil.color(message))); + break; + default: + sender.sendMessage(new TextComponent(ChatColor.YELLOW + ACFBungeeUtil.color(message))); + } } @Override diff --git a/core/acf-core.iml b/core/acf-core.iml index 6dbf4a62..143048f3 100644 --- a/core/acf-core.iml +++ b/core/acf-core.iml @@ -17,6 +17,6 @@ - + \ No newline at end of file diff --git a/core/pom.xml b/core/pom.xml index 18696a9e..0c241d05 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -46,6 +46,11 @@ 15.0 provided + + org.jetbrains + annotations + 15.0 + diff --git a/core/src/main/java/co/aikar/commands/BaseCommand.java b/core/src/main/java/co/aikar/commands/BaseCommand.java index 6dc081cd..fa7e32d0 100644 --- a/core/src/main/java/co/aikar/commands/BaseCommand.java +++ b/core/src/main/java/co/aikar/commands/BaseCommand.java @@ -315,7 +315,7 @@ public abstract class BaseCommand { public void execute(CommandIssuer issuer, String commandLabel, String[] args) { if (!this.hasPermission(issuer)) { - issuer.sendMessage(permissionMessage); + issuer.sendMessage(MessageType.ERROR, permissionMessage); return; } commandLabel = commandLabel.toLowerCase(); @@ -393,7 +393,7 @@ public abstract class BaseCommand { List sargs = Lists.newArrayList(args); cmd.invoke(issuer, sargs); } else { - issuer.sendMessage(cmd.scope.permissionMessage); + issuer.sendMessage(MessageType.ERROR, cmd.scope.permissionMessage); } } @@ -489,7 +489,7 @@ public abstract class BaseCommand { help(manager.getCommandIssuer(issuer), args); } public void help(CommandIssuer issuer, String[] args) { - issuer.sendMessage("&cUnknown Command, please type &f/help"); + issuer.sendMessage(MessageType.ERROR, "&cUnknown Command, please type &f/help"); } public void doHelp(Object issuer, String... args) { doHelp(manager.getCommandIssuer(issuer), args); @@ -499,7 +499,7 @@ public abstract class BaseCommand { } public void showSyntax(CommandIssuer issuer, RegisteredCommand cmd) { - issuer.sendMessage("&cUsage: /" + cmd.command + " " + cmd.syntaxText); + issuer.sendMessage(MessageType.SYNTAX, "&cUsage: /" + cmd.command + " " + cmd.syntaxText); } public boolean hasPermission(Object issuer) { diff --git a/core/src/main/java/co/aikar/commands/CommandContexts.java b/core/src/main/java/co/aikar/commands/CommandContexts.java index 1c390510..8f34b2a5 100644 --- a/core/src/main/java/co/aikar/commands/CommandContexts.java +++ b/core/src/main/java/co/aikar/commands/CommandContexts.java @@ -38,7 +38,7 @@ import java.util.Map; @SuppressWarnings("WeakerAccess") public class CommandContexts > { protected final Map, ContextResolver> contextMap = Maps.newHashMap(); - private final CommandManager manager; + protected final CommandManager manager; CommandContexts(CommandManager manager) { this.manager = manager; diff --git a/core/src/main/java/co/aikar/commands/CommandIssuer.java b/core/src/main/java/co/aikar/commands/CommandIssuer.java index cc15d3d7..646b85d4 100644 --- a/core/src/main/java/co/aikar/commands/CommandIssuer.java +++ b/core/src/main/java/co/aikar/commands/CommandIssuer.java @@ -41,7 +41,9 @@ public interface CommandIssuer { * Send the Command Issuer a message * @param message */ - void sendMessage(String message); + default void sendMessage(String message) { + sendMessage(MessageType.ERROR, message); + } /** * Has permission node @@ -49,4 +51,6 @@ public interface CommandIssuer { * @return */ boolean hasPermission(String permission); + + void sendMessage(MessageType type, String message); } diff --git a/core/src/main/java/co/aikar/commands/CommandManager.java b/core/src/main/java/co/aikar/commands/CommandManager.java index 29a09f97..fd527b6d 100644 --- a/core/src/main/java/co/aikar/commands/CommandManager.java +++ b/core/src/main/java/co/aikar/commands/CommandManager.java @@ -27,6 +27,7 @@ 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; @SuppressWarnings("WeakerAccess") @@ -34,6 +35,7 @@ abstract class CommandManager { protected Map rootCommands = new HashMap<>(); protected CommandReplacements replacements = new CommandReplacements(this); + protected Locales locales = new Locales(this); protected ExceptionHandler defaultExceptionHandler = null; /** @@ -48,6 +50,28 @@ abstract class CommandManager { */ public abstract CommandCompletions getCommandCompletions(); + /** + * Registers a command with ACF + * + * @param command The command to register + * @return boolean + */ + public abstract void registerCommand(BaseCommand command); + public abstract boolean hasRegisteredCommands(); + public abstract boolean isCommandIssuer(Class type); + + public abstract CommandIssuer getCommandIssuer(Object issuer); + + public abstract RootCommand createRootCommand(String cmd); + + public abstract R createCommandContext(RegisteredCommand command, Parameter parameter, CommandIssuer sender, List args, int i, Map passedArgs); + + public abstract CommandCompletionContext createCompletionContext(RegisteredCommand command, CommandIssuer sender, String input, String config, String[] args); + + public abstract void log(final LogLevel level, final String message); + + public abstract void log(final LogLevel level, final String message, final Throwable throwable); + /** * Lets you add custom string replacements that can be applied to annotation values, * to reduce duplication/repetition of common values such as permission nodes and command prefixes. @@ -62,37 +86,25 @@ abstract class CommandManager { } /** - * Registers a command with ACF - * - * @param command The command to register - * @return boolean + * Returns a Locales Manager to add and modify language tables for your commands. + * @return */ - public abstract void registerCommand(BaseCommand command); - public abstract boolean hasRegisteredCommands(); - public abstract boolean isCommandIssuer(Class type); + Locales getLocales() { + return locales; + } public boolean hasPermission(CommandIssuer issuer, String permission) { return permission == null || permission.isEmpty() || issuer.hasPermission(permission); } - public abstract CommandIssuer getCommandIssuer(Object issuer); - - public abstract RootCommand createRootCommand(String cmd); - public synchronized RootCommand obtainRootCommand(String cmd) { return rootCommands.computeIfAbsent(cmd.toLowerCase(), this::createRootCommand); } - public abstract R createCommandContext(RegisteredCommand command, Parameter parameter, CommandIssuer sender, List args, int i, Map passedArgs); - - public abstract CommandCompletionContext createCompletionContext(RegisteredCommand command, CommandIssuer sender, String input, String config, String[] args); - public RegisteredCommand createRegisteredCommand(BaseCommand command, String cmdName, Method method, String prefSubCommand) { return new RegisteredCommand(command, cmdName, method, prefSubCommand); } - public abstract void log(final LogLevel level, final String message); - public abstract void log(final LogLevel level, final String message, final Throwable throwable); /** * Sets the default {@link ExceptionHandler} that is called when an exception occurs while executing a command, if the command doesn't have it's own exception handler registered. @@ -121,4 +133,19 @@ abstract class CommandManager { } return result; } + + protected void sendMessage(Object issuerArg, MessageType type, MessageKey key, String... replacements) { + CommandIssuer issuer = issuerArg instanceof CommandIssuer ? (CommandIssuer) issuerArg : getCommandIssuer(issuerArg); + Locale locale = getIssuerLocale(issuer); + String message = getLocales().getMessage(locale, key); + if (replacements.length > 0) { + message = ACFUtil.replaceStrings(message, replacements); + } + // TODO: Colors? + issuer.sendMessage(type, message); + } + + public Locale getIssuerLocale(CommandIssuer issuer) { + return getLocales().getDefaultLocale(); + } } diff --git a/core/src/main/java/co/aikar/commands/LanguageTable.java b/core/src/main/java/co/aikar/commands/LanguageTable.java new file mode 100644 index 00000000..6166f4b6 --- /dev/null +++ b/core/src/main/java/co/aikar/commands/LanguageTable.java @@ -0,0 +1,52 @@ +/* + * 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 com.google.common.collect.Maps; +import org.jetbrains.annotations.NotNull; + +import java.util.Locale; +import java.util.Map; + +class LanguageTable { + + private final Locale locale; + private final Map messages = Maps.newHashMap(); + + LanguageTable(Locale locale) { + this.locale = locale; + } + + public String addMessage(MessageKey key, String message) { + return messages.put(key, message); + } + + public String getMessage(MessageKey key) { + return messages.get(key); + } + + public void addMessages(@NotNull Map messages) { + this.messages.putAll(messages); + } +} diff --git a/core/src/main/java/co/aikar/commands/Locales.java b/core/src/main/java/co/aikar/commands/Locales.java new file mode 100644 index 00000000..0d6ee7bb --- /dev/null +++ b/core/src/main/java/co/aikar/commands/Locales.java @@ -0,0 +1,97 @@ +/* + * 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 com.google.common.collect.Maps; +import org.jetbrains.annotations.NotNull; + +import java.util.Locale; +import java.util.Map; +import java.util.Objects; + +/** + * This isn't public yet, still WIP - API will break + * @deprecated + */ +@SuppressWarnings("WeakerAccess") +@Deprecated +public class Locales { + + private Locale defaultLocale = Locale.ENGLISH; + private final CommandManager manager; + private final Map tables = Maps.newHashMap(); + + Locales(CommandManager manager) { + this.manager = manager; + this.initializeSystemMessages(); + } + + private void initializeSystemMessages() { + LanguageTable table = getTable(Locale.ENGLISH); + //table.addMessage(MessageKey.FOO, "bar"); + } + + /** + * Changes the default locale to use if the specified language key is missing for the desired locale + * @param locale + * @return Previous default locale + */ + public Locale setDefaultLocale(Locale locale) { + Locale prev = this.defaultLocale; + this.defaultLocale = locale; + return prev; + } + + public Locale getDefaultLocale() { + return defaultLocale; + } + + public void addMessages(Locale locale, @NotNull Map messages) { + getTable(locale).addMessages(messages); + } + + public String addMessage(Locale locale, MessageKey key, String message) { + return getTable(locale).addMessage(key, message); + } + + public String getMessage(Locale locale, MessageKey key) { + String message = getTable(locale).getMessage(key); + if (message == null && !Objects.equals(locale, defaultLocale)) { + message = getTable(defaultLocale).getMessage(key); + } + if (message == null && !Objects.equals(Locale.ENGLISH, defaultLocale) && !Objects.equals(Locale.ENGLISH, locale)) { + message = getTable(Locale.ENGLISH).getMessage(key); + } + if (message == null) { + manager.log(LogLevel.ERROR, "Missing Language Key: " + key); + message = ""; + } + return message; + } + + public LanguageTable getTable(Locale locale) { + return tables.computeIfAbsent(locale, (l) -> new LanguageTable(locale)); + } + +} diff --git a/core/src/main/java/co/aikar/commands/MessageKey.java b/core/src/main/java/co/aikar/commands/MessageKey.java new file mode 100644 index 00000000..84df0719 --- /dev/null +++ b/core/src/main/java/co/aikar/commands/MessageKey.java @@ -0,0 +1,56 @@ +/* + * 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.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; + +public class MessageKey { + private static final AtomicInteger counter = new AtomicInteger(); + private static final Map keyMap = new ConcurrentHashMap<>(); + private final int id = counter.getAndIncrement(); + private final String key; + + private MessageKey(String key) { + this.key = key; + } + + public static MessageKey of(String key) { + return keyMap.computeIfAbsent(key.toLowerCase(), MessageKey::new); + } + + public int hashCode() { + return id; + } + + @Override + public boolean equals(Object o) { + return (this == o); + } + + public String getKey() { + return key; + } +} diff --git a/core/src/main/java/co/aikar/commands/MessageType.java b/core/src/main/java/co/aikar/commands/MessageType.java new file mode 100644 index 00000000..490f4878 --- /dev/null +++ b/core/src/main/java/co/aikar/commands/MessageType.java @@ -0,0 +1,28 @@ +/* + * 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; + +public enum MessageType { + INFO, SYNTAX, ERROR +} diff --git a/core/src/main/java/co/aikar/commands/RegisteredCommand.java b/core/src/main/java/co/aikar/commands/RegisteredCommand.java index 2b4e69de..bfeaff70 100644 --- a/core/src/main/java/co/aikar/commands/RegisteredCommand.java +++ b/core/src/main/java/co/aikar/commands/RegisteredCommand.java @@ -154,7 +154,7 @@ public class RegisteredCommand { - sender.sendMessage("An error occured. This problem has been logged. Sorry for the inconvienence."); + sender.sendMessage(MessageType.ERROR, "An error occured. This problem has been logged. Sorry for the inconvienence."); return true; // mark as handeled, default message will not be send to sender })); commandManager.registerCommand(new SomeCommand_ExtraSubs()); diff --git a/sponge/src/main/java/co/aikar/commands/SpongeCommandIssuer.java b/sponge/src/main/java/co/aikar/commands/SpongeCommandIssuer.java index e1eb467c..ca8b9b38 100644 --- a/sponge/src/main/java/co/aikar/commands/SpongeCommandIssuer.java +++ b/sponge/src/main/java/co/aikar/commands/SpongeCommandIssuer.java @@ -25,6 +25,9 @@ package co.aikar.commands; import org.spongepowered.api.command.CommandSource; import org.spongepowered.api.entity.living.player.Player; +import org.spongepowered.api.text.Text; +import org.spongepowered.api.text.format.TextColor; +import org.spongepowered.api.text.format.TextColors; import org.spongepowered.api.text.serializer.TextSerializers; public class SpongeCommandIssuer implements CommandIssuer { @@ -47,8 +50,15 @@ public class SpongeCommandIssuer implements CommandIssuer { } @Override - public void sendMessage(final String message) { - this.source.sendMessage(TextSerializers.FORMATTING_CODE.deserialize(message)); + public void sendMessage(MessageType type, String message) { + switch (type) { + case ERROR: + case SYNTAX: + this.source.sendMessage(Text.of(TextColors.RED, TextSerializers.LEGACY_FORMATTING_CODE.stripCodes(message))); + break; + default: + this.source.sendMessage(Text.of(TextColors.YELLOW, TextSerializers.LEGACY_FORMATTING_CODE.stripCodes(message))); + } } @Override