* The command works using a list of valid syntaxes.
* For instance we could build the command
* "/health set Notch 50" into multiple argument types "/health [set/add/remove] [username] [integer]"
*
* All the default argument types can be found in {@link ArgumentType}
* and the syntax be created/registered using {@link #addSyntax(CommandExecutor, Argument[])}.
*
* If the command is executed with an incorrect syntax or without any argument, the default {@link CommandExecutor} will be called,
* you can set it using {@link #setDefaultExecutor(CommandExecutor)}.
*
* Before any syntax to be successfully executed the {@link CommandSender} needs to validated
* the {@link CommandCondition} sets with {@link #setCondition(CommandCondition)} (ignored if null).
*
* Some {@link Argument} could also require additional condition (eg: a number which need to be between 2 values),
* in this case, if the whole syntax is correct but not the argument condition,
* you can listen to its error code using {@link #setArgumentCallback(ArgumentCallback, Argument)} or {@link Argument#setCallback(ArgumentCallback)}.
*/
public class Command {
public final static Logger LOGGER = LoggerFactory.getLogger(Command.class);
private final String name;
private final String[] aliases;
private final String[] names;
private CommandExecutor defaultExecutor;
private CommandCondition condition;
private final List subcommands;
private final List syntaxes;
/**
* Creates a {@link Command} with a name and one or multiple aliases.
*
* @param name the name of the command
* @param aliases the command aliases
* @see #Command(String)
*/
public Command(@NotNull String name, @Nullable String... aliases) {
this.name = name;
this.aliases = aliases;
this.names = Stream.concat(Arrays.stream(aliases), Stream.of(name)).toArray(String[]::new);
this.subcommands = new ArrayList<>();
this.syntaxes = new ArrayList<>();
}
/**
* Creates a {@link Command} with a name and no alias.
*
* @param name the name of the command
* @see #Command(String, String...)
*/
public Command(@NotNull String name) {
this(name, new String[0]);
}
/**
* Gets the {@link CommandCondition}.
*
* It is called after the parsing and just before the execution no matter the syntax used and can be used to check permissions or
* the {@link CommandSender} type.
*
* Worth mentioning that the condition is also used to know if the command known from a player (at connection).
*
* @return the command condition, null if not any
*/
@Nullable
public CommandCondition getCondition() {
return condition;
}
/**
* Sets the {@link CommandCondition}.
*
* @param commandCondition the new command condition, null to do not call anything
* @see #getCondition()
*/
public void setCondition(@Nullable CommandCondition commandCondition) {
this.condition = commandCondition;
}
/**
* Sets an {@link ArgumentCallback}.
*
* The argument callback is called when there's an error in the argument.
*
* @param callback the callback for the argument
* @param argument the argument which get the callback
*/
public void setArgumentCallback(@NotNull ArgumentCallback callback, @NotNull Argument> argument) {
argument.setCallback(callback);
}
public void addSubcommand(@NotNull Command command) {
this.subcommands.add(command);
}
@NotNull
public List getSubcommands() {
return Collections.unmodifiableList(subcommands);
}
/**
* Adds a new syntax in the command.
*
* A syntax is simply a list of arguments and an executor called when successfully parsed.
*
* @param commandCondition the condition to use the syntax
* @param executor the executor to call when the syntax is successfully received
* @param args all the arguments of the syntax, the length needs to be higher than 0
* @return the created {@link CommandSyntax syntaxes},
* there can be multiple of them when optional arguments are used
*/
@NotNull
public Collection addConditionalSyntax(@Nullable CommandCondition commandCondition,
@NotNull CommandExecutor executor,
@NotNull Argument>... args) {
// Check optional argument(s)
boolean hasOptional = false;
{
for (Argument> argument : args) {
if (argument.isOptional()) {
hasOptional = true;
}
if (hasOptional && !argument.isOptional()) {
LOGGER.warn("Optional arguments are followed by a non-optional one, the default values will be ignored.");
hasOptional = false;
break;
}
}
}
if (!hasOptional) {
final CommandSyntax syntax = new CommandSyntax(commandCondition, executor, args);
this.syntaxes.add(syntax);
return List.of(syntax);
} else {
List optionalSyntaxes = new ArrayList<>();
// the 'args' array starts by all the required arguments, followed by the optional ones
List> requiredArguments = new ArrayList<>();
Map> defaultValuesMap = new HashMap<>();
boolean optionalBranch = false;
int i = 0;
for (Argument> argument : args) {
final boolean isLast = ++i == args.length;
if (argument.isOptional()) {
// Set default value
defaultValuesMap.put(argument.getId(), (Supplier