SenderProvider<T> is a functional interface that converts the generic command sender into platform-specific sender types. It enables type-safe sender injection in command methods, allowing you to receive Player, ConsoleCommandSender, or custom sender types directly as parameters.
Interface Definition
@FunctionalInterface
public interface SenderProvider<T> {
@Nullable
T provide(@NotNull Context context, @NotNull Sender<?> sender) throws BladeParseError;
}
Core Method
provide()
Converts the generic command sender into a specific type.
The command execution context containing metadata and command information
The generic command sender to convert
The converted sender value, or null if conversion is not possible
Throws: BladeParseError if the sender cannot be converted (usually indicates wrong sender type)
Example Implementation
public class PlayerSenderProvider implements SenderProvider<Player> {
@Override
public Player provide(@NotNull Context context, @NotNull Sender<?> sender)
throws BladeParseError {
if (sender.isConsole()) {
throw BladeParseError.fatal("This command can only be used by players.");
}
Object underlying = sender.underlying();
if (underlying instanceof Player) {
return (Player) underlying;
}
throw BladeParseError.fatal("Unable to convert sender to Player.");
}
@Override
public String friendlyName(boolean plural) {
return plural ? "players" : "player";
}
}
Default Methods
friendlyName()
Provides a user-friendly name for this sender type, used in error messages.
default String friendlyName(boolean plural) {
return null;
}
Whether the name should be in plural form
The friendly name for this sender type, or null to use a default name
This name appears in permission error messages and usage restrictions. For example:
- Singular: “This command can only be used by a player”
- Plural: “This command can only be used by players”
Example
public class ConsoleSenderProvider implements SenderProvider<ConsoleCommandSender> {
@Override
public String friendlyName(boolean plural) {
return plural ? "console senders" : "console";
}
@Override
public ConsoleCommandSender provide(@NotNull Context context,
@NotNull Sender<?> sender)
throws BladeParseError {
if (!sender.isConsole()) {
// Uses friendlyName() in error message:
// "This command can only be used by console"
throw BladeParseError.fatal(
"This command can only be used by " + friendlyName(false) + "."
);
}
return (ConsoleCommandSender) sender.underlying();
}
}
Common Use Cases
Player-Only Commands
Restrict commands to player senders:
public class PlayerSenderProvider implements SenderProvider<Player> {
@Override
public Player provide(@NotNull Context context, @NotNull Sender<?> sender)
throws BladeParseError {
if (sender.isConsole()) {
throw BladeParseError.fatal("Only players can use this command.");
}
return (Player) sender.underlying();
}
@Override
public String friendlyName(boolean plural) {
return plural ? "players" : "player";
}
}
Console-Only Commands
Restrict commands to console senders:
public class ConsoleSenderProvider implements SenderProvider<ConsoleCommandSender> {
@Override
public ConsoleCommandSender provide(@NotNull Context context,
@NotNull Sender<?> sender)
throws BladeParseError {
if (!sender.isConsole()) {
throw BladeParseError.fatal("This command can only be run from console.");
}
return (ConsoleCommandSender) sender.underlying();
}
}
Permission-Based Sender Types
Create custom sender types with permission checks:
public class AdminPlayer {
private final Player player;
public AdminPlayer(Player player) {
this.player = player;
}
public Player getPlayer() {
return player;
}
}
public class AdminPlayerProvider implements SenderProvider<AdminPlayer> {
@Override
public AdminPlayer provide(@NotNull Context context, @NotNull Sender<?> sender)
throws BladeParseError {
if (sender.isConsole()) {
throw BladeParseError.fatal("Only players can use this command.");
}
Player player = (Player) sender.underlying();
if (!player.hasPermission("server.admin")) {
throw BladeParseError.fatal("You must be an admin to use this command.");
}
return new AdminPlayer(player);
}
@Override
public String friendlyName(boolean plural) {
return plural ? "administrators" : "administrator";
}
}
Handle different platform sender types:
public class UniversalPlayerProvider implements SenderProvider<UniversalPlayer> {
@Override
public UniversalPlayer provide(@NotNull Context context, @NotNull Sender<?> sender)
throws BladeParseError {
Object underlying = sender.underlying();
// Bukkit
if (underlying instanceof org.bukkit.entity.Player) {
return new BukkitPlayer((org.bukkit.entity.Player) underlying);
}
// BungeeCord
if (underlying instanceof net.md_5.bungee.api.connection.ProxiedPlayer) {
return new BungeePlayer((ProxiedPlayer) underlying);
}
// Velocity
if (underlying instanceof com.velocitypowered.api.proxy.Player) {
return new VelocityPlayer((com.velocitypowered.api.proxy.Player) underlying);
}
throw BladeParseError.fatal("Unknown sender type: " + underlying.getClass());
}
}
Error Handling
Use BladeParseError to report sender conversion errors:
Fatal Errors
Fatal errors stop command execution and display the error message:
if (sender.isConsole()) {
throw BladeParseError.fatal("This command cannot be used from console.");
}
Type Checking
Always verify the sender type before casting:
Object underlying = sender.underlying();
if (!(underlying instanceof Player)) {
throw BladeParseError.fatal(
"Expected Player but got " + underlying.getClass().getSimpleName()
);
}
Null Handling
Returning null from provide() will cause command execution to fail silently. Prefer throwing BladeParseError with descriptive messages:
// Bad: Silent failure
@Override
public Player provide(@NotNull Context context, @NotNull Sender<?> sender) {
if (sender.isConsole()) return null; // No feedback to user
return (Player) sender.underlying();
}
// Good: Clear error message
@Override
public Player provide(@NotNull Context context, @NotNull Sender<?> sender)
throws BladeParseError {
if (sender.isConsole()) {
throw BladeParseError.fatal("Only players can use this command.");
}
return (Player) sender.underlying();
}
Registration
Register sender providers with the Blade instance:
Blade.of()
.bindSender(Player.class, new PlayerSenderProvider())
.bindSender(ConsoleCommandSender.class, new ConsoleSenderProvider())
.bindSender(AdminPlayer.class, new AdminPlayerProvider())
.build();
Usage in Commands
Once registered, use the sender type directly in command method parameters:
@Command("teleport")
public void teleport(Player sender, Player target) {
// 'sender' is automatically injected using PlayerSenderProvider
sender.teleport(target.getLocation());
}
@Command("stop")
public void stop(ConsoleCommandSender console) {
// Only console can execute this command
console.sendMessage("Stopping server...");
}
@Command("admin")
public void adminCommand(AdminPlayer admin) {
// Only players with admin permission can execute
admin.getPlayer().sendMessage("Admin command executed!");
}