Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import fr.traqueur.commands.api.arguments.Arguments;
import fr.traqueur.commands.api.arguments.TabCompleter;
import fr.traqueur.commands.api.exceptions.ArgumentIncorrectException;
import fr.traqueur.commands.api.exceptions.CommandRegistrationException;
import fr.traqueur.commands.api.exceptions.TypeArgumentNotExistException;
import fr.traqueur.commands.api.logging.Logger;
import fr.traqueur.commands.api.logging.MessageHandler;
Expand Down Expand Up @@ -150,7 +151,7 @@ public void registerCommand(Command<T,S> command) {
this.registerSubCommands(alias, command.getSubcommands());
}
} catch(TypeArgumentNotExistException e) {
throw new RuntimeException(e);
throw new CommandRegistrationException("Failed to register command: " + command.getClass().getSimpleName(), e);
}
}

Expand Down Expand Up @@ -299,8 +300,8 @@ private void registerSubCommands(String parentLabel, List<Command<T,S>> subcomma
return;
}
for (Command<T,S> subcommand : subcommands) {
// getAliases() already returns [name, ...aliases], so no need to add the name again
List<String> aliasesSub = new ArrayList<>(subcommand.getAliases());
aliasesSub.add(subcommand.getName());
for (String aliasSub : aliasesSub) {
this.addCommand(subcommand, parentLabel + "." + aliasSub);
this.registerSubCommands(parentLabel + "." + aliasSub, subcommand.getSubcommands());
Expand All @@ -318,8 +319,8 @@ private void unregisterSubCommands(String parentLabel, List<Command<T,S>> subcom
return;
}
for (Command<T,S> subcommand : subcommandsList) {
// getAliases() already returns [name, ...aliases], so no need to add the name again
List<String> aliasesSub = new ArrayList<>(subcommand.getAliases());
aliasesSub.add(subcommand.getName());
for (String aliasSub : aliasesSub) {
this.removeCommand(parentLabel + "." + aliasSub, true);
this.unregisterSubCommands(parentLabel + "." + aliasSub, subcommand.getSubcommands());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@ public class Arguments {
/**
* The map of the arguments.
*/
private final Map<String, ArgumentValue> arguments;
protected final Map<String, ArgumentValue> arguments;

/**
* The logger of the class.
*/
private final Logger logger;
protected final Logger logger;

/**
* Constructor of the class.
Expand Down Expand Up @@ -374,4 +374,23 @@ public void add(String key, Class<?> type, Object object) {
ArgumentValue argumentValue = new ArgumentValue(type, object);
this.arguments.put(key, argumentValue);
}

/**
* Check if an argument exists in the map.
*
* @param key The key of the argument.
* @return true if the argument exists, false otherwise.
*/
public boolean has(String key) {
return this.arguments.containsKey(key);
}

/**
* Get the logger of the class.
*
* @return The logger of the class.
*/
protected Logger getLogger() {
return this.logger;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package fr.traqueur.commands.api.exceptions;

/**
* Exception thrown when a command registration fails.
* This is a runtime exception as registration failures are typically unrecoverable.
*/
public class CommandRegistrationException extends RuntimeException {

/**
* Constructs a new exception with the specified detail message.
*
* @param message the detail message
*/
public CommandRegistrationException(String message) {
super(message);
}

/**
* Constructs a new exception with the specified detail message and cause.
*
* @param message the detail message
* @param cause the cause of the exception
*/
public CommandRegistrationException(String message, Throwable cause) {
super(message, cause);
}

/**
* Constructs a new exception with the specified cause.
*
* @param cause the cause of the exception
*/
public CommandRegistrationException(Throwable cause) {
super(cause);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package fr.traqueur.commands.api.exceptions;

/**
* Exception thrown when the updater fails to initialize.
* This is a runtime exception as initialization failures are typically unrecoverable.
*/
public class UpdaterInitializationException extends RuntimeException {

/**
* Constructs a new exception with the specified detail message.
*
* @param message the detail message
*/
public UpdaterInitializationException(String message) {
super(message);
}

/**
* Constructs a new exception with the specified detail message and cause.
*
* @param message the detail message
* @param cause the cause of the exception
*/
public UpdaterInitializationException(String message, Throwable cause) {
super(message, cause);
}

/**
* Constructs a new exception with the specified cause.
*
* @param cause the cause of the exception
*/
public UpdaterInitializationException(Throwable cause) {
super(cause);
}
}
196 changes: 171 additions & 25 deletions core/src/main/java/fr/traqueur/commands/api/models/CommandInvoker.java
Original file line number Diff line number Diff line change
Expand Up @@ -41,63 +41,209 @@ public CommandInvoker(CommandManager<T, S> manager) {
* @return true if a command handler was executed or a message sent; false if command not found
*/
public boolean invoke(S source, String base, String[] rawArgs) {
// find matching node
Optional<CommandContext<T, S>> contextOpt = findCommandContext(base, rawArgs);
if (!contextOpt.isPresent()) {
return false;
}

CommandContext<T, S> context = contextOpt.get();

if (!validateCommandExecution(source, context)) {
return true;
}

return executeCommand(source, context);
}

/**
* Find and prepare command context.
* @param base the base command label
* @param rawArgs the raw arguments
* @return the command context if found
*/
private Optional<CommandContext<T, S>> findCommandContext(String base, String[] rawArgs) {
Optional<MatchResult<T, S>> found = manager.getCommands().findNode(base, rawArgs);
if (!found.isPresent()) return false;
if (!found.isPresent()) {
return Optional.empty();
}

MatchResult<T, S> result = found.get();
CommandTree.CommandNode<T, S> node = result.node;
Optional<Command<T, S>> cmdOpt = node.getCommand();
if (!cmdOpt.isPresent()) return false;

if (!cmdOpt.isPresent()) {
return Optional.empty();
}

Command<T, S> command = cmdOpt.get();
String label = node.getFullLabel() != null ? node.getFullLabel() : base;
String[] args = result.args;

// in-game check
return Optional.of(new CommandContext<>(command, label, args));
}

/**
* Validate command execution conditions (in-game, permissions, requirements, usage).
* @param source the command sender
* @param context the command context
* @return true if all validations passed, false otherwise (message already sent to user)
*/
private boolean validateCommandExecution(S source, CommandContext<T, S> context) {
return checkInGameOnly(source, context.command)
&& checkPermission(source, context.command)
&& checkRequirements(source, context.command)
&& checkUsage(source, context);
}

/**
* Check if command requires in-game execution.
* @param source the command sender
* @param command the command to check
* @return true if check passed or not applicable
*/
private boolean checkInGameOnly(S source, Command<T, S> command) {
if (command.inGameOnly() && !manager.getPlatform().isPlayer(source)) {
manager.getPlatform().sendMessage(source, manager.getMessageHandler().getOnlyInGameMessage());
return true;
return false;
}
// permission check
return true;
}

/**
* Check if sender has required permission.
* @param source the command sender
* @param command the command to check
* @return true if check passed or no permission required
*/
private boolean checkPermission(S source, Command<T, S> command) {
String perm = command.getPermission();
if (!perm.isEmpty() && !manager.getPlatform().hasPermission(source, perm)) {
manager.getPlatform().sendMessage(source, manager.getMessageHandler().getNoPermissionMessage());
return true;
return false;
}
// requirements
return true;
}

/**
* Check if all requirements are satisfied.
* @param source the command sender
* @param command the command to check
* @return true if all requirements passed
*/
private boolean checkRequirements(S source, Command<T, S> command) {
for (Requirement<S> req : command.getRequirements()) {
if (!req.check(source)) {
String msg = req.errorMessage().isEmpty()
? manager.getMessageHandler().getRequirementMessage().replace("%requirement%", req.getClass().getSimpleName())
: req.errorMessage();
String msg = buildRequirementMessage(req);
manager.getPlatform().sendMessage(source, msg);
return true;
return false;
}
}
// usage check
return true;
}

/**
* Build error message for failed requirement.
* @param req the failed requirement
* @return the error message
*/
private String buildRequirementMessage(Requirement<S> req) {
return req.errorMessage().isEmpty()
? manager.getMessageHandler().getRequirementMessage()
.replace("%requirement%", req.getClass().getSimpleName())
: req.errorMessage();
}

/**
* Check if argument count is valid.
* @param source the command sender
* @param context the command context
* @return true if usage is correct
*/
private boolean checkUsage(S source, CommandContext<T, S> context) {
Command<T, S> command = context.command;
String[] args = context.args;

int min = command.getArgs().size();
int max = command.isInfiniteArgs() ? Integer.MAX_VALUE : min + command.getOptinalArgs().size();

if (args.length < min || args.length > max) {
String usage = command.getUsage().isEmpty()
? command.generateDefaultUsage(manager.getPlatform(), source, label)
: command.getUsage();
String usage = buildUsageMessage(source, context);
manager.getPlatform().sendMessage(source, usage);
return true;
return false;
}
// parse and execute
return true;
}

/**
* Build usage message for command.
* @param source the command sender
* @param context the command context
* @return the usage message
*/
private String buildUsageMessage(S source, CommandContext<T, S> context) {
Command<T, S> command = context.command;
String label = context.label;

return command.getUsage().isEmpty()
? command.generateDefaultUsage(manager.getPlatform(), source, label)
: command.getUsage();
}

/**
* Execute the command with error handling.
* @param source the command sender
* @param context the command context
* @return true if execution succeeded or error was handled, false for internal errors
*/
private boolean executeCommand(S source, CommandContext<T, S> context) {
try {
Arguments parsed = manager.parse(command, args);
command.execute(source, parsed);
Arguments parsed = manager.parse(context.command, context.args);
context.command.execute(source, parsed);
return true;
} catch (TypeArgumentNotExistException e) {
manager.getPlatform().sendMessage(source, "&cInternal error: invalid argument type");
return false;
return handleTypeArgumentError(source);
} catch (ArgumentIncorrectException e) {
String msg = manager.getMessageHandler().getArgNotRecognized().replace("%arg%", e.getInput());
manager.getPlatform().sendMessage(source, msg);
return true;
return handleArgumentIncorrectError(source, e);
}
}

/**
* Handle type argument not exist error.
* @param source the command sender
* @return false to indicate internal error
*/
private boolean handleTypeArgumentError(S source) {
manager.getPlatform().sendMessage(source, "&cInternal error: invalid argument type");
return false;
}

/**
* Handle incorrect argument error.
* @param source the command sender
* @param e the exception
* @return true to indicate error was handled
*/
private boolean handleArgumentIncorrectError(S source, ArgumentIncorrectException e) {
String msg = manager.getMessageHandler().getArgNotRecognized().replace("%arg%", e.getInput());
manager.getPlatform().sendMessage(source, msg);
return true;
}

/**
* Internal context class to hold command execution data.
*/
private static class CommandContext<T, S> {
final Command<T, S> command;
final String label;
final String[] args;

CommandContext(Command<T, S> command, String label, String[] args) {
this.command = command;
this.label = label;
this.args = args;
}
}

/**
* Suggests command completions based on the provided source, base label, and arguments.
* This method checks for available tab completers and filters suggestions based on the current input.
Expand Down
Loading