blob: 994c864bb39609cc7fcc339aa49d5dfa36d9921e [file] [log] [blame] [raw]
package net.glowstone.command.minecraft;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import java.util.List;
import java.util.Locale;
import java.util.ResourceBundle;
import java.util.concurrent.ExecutionException;
import lombok.Data;
import net.glowstone.entity.GlowPlayer;
import net.glowstone.i18n.ConsoleMessages;
import net.glowstone.i18n.LocalizedString;
import net.glowstone.i18n.LocalizedStringImpl;
import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender;
import org.bukkit.command.defaults.VanillaCommand;
import org.jetbrains.annotations.NonNls;
/**
* A subclass of {@link VanillaCommand} with the additional feature that when the command sender is
* a {@link GlowPlayer}, the description, usage and permission-error messages are looked up in the
* client's locale, overriding whatever has been or is subsequently set in
* {@link #setDescription(String)}, {@link #setUsage(String)} or
* {@link #setPermissionMessage(String)}. For non-player command senders and players with unknown
* locale ({@code {@link GlowPlayer#getLocale()} == null}), messages set with these setters will be
* used, and the initial values are based on the server's locale.
*/
public abstract class GlowVanillaCommand extends VanillaCommand {
@NonNls
private static final String BUNDLE_BASE_NAME = "commands";
@NonNls
private static final String DESCRIPTION_SUFFIX = ".description";
@NonNls
private static final String USAGE_SUFFIX = ".usage";
@NonNls
private static final String PERMISSION_SUFFIX = ".no-permission";
@NonNls
private static final String DEFAULT_PERMISSION = "_generic.no-permission";
@NonNls
private static final String NO_SUCH_PLAYER = "_generic.no-such-player";
@NonNls
private static final String USAGE_IS = "_generic.usage";
private static final ResourceBundle SERVER_LOCALE_BUNDLE
= ResourceBundle.getBundle(BUNDLE_BASE_NAME);
private static final long CACHE_SIZE = 50;
private static final LoadingCache<String, ResourceBundle> STRING_TO_BUNDLE_CACHE
= CacheBuilder.newBuilder()
.maximumSize(CACHE_SIZE)
.build(CacheLoader.from(localeStr ->
ResourceBundle.getBundle(BUNDLE_BASE_NAME, Locale.forLanguageTag(localeStr))));
private final LoadingCache<ResourceBundle, CommandMessages> bundleToMessageCache;
protected CommandMessages readResourceBundle(ResourceBundle bundle) {
String name = getName();
String permissionKey = name + PERMISSION_SUFFIX;
return new CommandMessages(
bundle.getString(name + DESCRIPTION_SUFFIX),
bundle.getString(name + USAGE_SUFFIX),
bundle.getString(
bundle.containsKey(permissionKey) ? permissionKey : DEFAULT_PERMISSION),
new LocalizedStringImpl(NO_SUCH_PLAYER, bundle));
}
/**
* Creates an instance, using the command's name to look up the description etc.
*/
public GlowVanillaCommand(@NonNls String name, @NonNls List<String> aliases) {
super(name, "", "", aliases);
bundleToMessageCache = CacheBuilder.newBuilder().maximumSize(CACHE_SIZE).build(
CacheLoader.from(this::readResourceBundle));
CommandMessages defaultMessages = readResourceBundle(SERVER_LOCALE_BUNDLE);
super.setDescription(defaultMessages.getDescription());
super.setUsage(defaultMessages.getUsageMessage());
super.setPermissionMessage(defaultMessages.getPermissionMessage());
}
/**
* {@inheritDoc}
* <p>This delegates to {@link #execute(CommandSender, String, String[], ResourceBundle,
* CommandMessages)}. If the command sender is a player, then the description and usage message
* are for that player's locale; otherwise, the server locale is used.</p>
*/
@Override
public boolean execute(CommandSender sender, String commandLabel, String[] args) {
CommandMessages localizedMessages = null;
ResourceBundle bundle = null;
if (sender instanceof GlowPlayer) {
try {
bundle = getBundle((GlowPlayer) sender);
localizedMessages = bundleToMessageCache.get(bundle);
} catch (ExecutionException e) {
ConsoleMessages.Warn.Command.L10N_FAILED.log(e, getName(), sender);
}
}
if (bundle == null) {
bundle = SERVER_LOCALE_BUNDLE;
}
if (localizedMessages == null) {
localizedMessages = new CommandMessages(
getDescription(),
getUsage(),
getPermissionMessage(),
new LocalizedStringImpl("_generic.no-such-player", SERVER_LOCALE_BUNDLE));
}
return execute(sender, commandLabel, args, bundle, localizedMessages);
}
/**
* Executes the command, returning its success.
*
* @param sender Source object which is executing this command
* @param commandLabel The alias of the command used
* @param args All arguments passed to the command, split via ' '
* @param resourceBundle The {@code commands.properties} resource bundle for the sender's locale
* @param localizedMessages Object containing the title, description and permission message in
* the sender's locale, or set with setters
* @return true if the command was successful, otherwise false
*/
protected abstract boolean execute(CommandSender sender, String commandLabel, String[] args,
ResourceBundle resourceBundle, CommandMessages localizedMessages);
protected static ResourceBundle getBundle(GlowPlayer sender)
throws ExecutionException {
return STRING_TO_BUNDLE_CACHE.get(sender.getLocale());
}
/**
* Works like {@link #testPermission(CommandSender)} but uses the specified error message.
* @param target User to test
* @param permissionMessage Error message if user lacks permission
* @return true if they can use it, otherwise false
*/
public boolean testPermission(CommandSender target, String permissionMessage) {
if (testPermissionSilent(target)) {
return true;
}
target.sendMessage(ChatColor.RED + permissionMessage);
return false;
}
protected void sendUsageMessage(CommandSender sender, ResourceBundle resourceBundle) {
new LocalizedStringImpl(USAGE_IS, resourceBundle)
.sendInColor(ChatColor.RED, sender, usageMessage);
}
@Data
protected static class CommandMessages {
// Only LocalizedString messages that apply to multiple commands should be in this class.
// All others are instantiated on demand.
private final String description;
private final String usageMessage;
private final String permissionMessage;
private final LocalizedString noSuchPlayer;
}
}