I am making a Discord bot with several 'command features'
public class MyBotCommandFeature extends MyBotFeature {
@NotNull public final Set<MyBotCommand> commands;
public MyBotCommandFeature(@NotNull MyBotCommand @NotNull ... commands) {
if (commands.length == 0) {
this.commands = new HashSet<>();
} else {
this.commands = stream(commands).collect(Collectors.toSet());
}
}
@Nullable
@CheckForNull
@CheckReturnValue
public Set<MyBotCommand> getExtraCommands(@NotNull Guild guild) {
return null;
}
}
Each feature has several commands (MyBotCommand). MyBotCommand has two abstract methods for handling text commands (runTextCommand) and slash commands (runSlashCommand).
Because some commands will want to refer to the MyBotFeature
they're a part of, MyBotCommandFeature
is currently one of the parameters of these abstract methods. However, to use any feature-specific features, commands currently have to cast MyBotCommandFeature
to, say, ExampleCommandFeature
.
public abstract class MyBotCommand {
public abstract void runTextCommand(@NotNull MessageReceivedEvent event, @NotNull MyBotCommandFeature feature);
public abstract void runSlashCommand(@NotNull SlashCommandInteractionEvent event, @NotNull MyBotCommandFeature feature);
}
public final class ExampleFeature extends MyBotCommandFeature {
public ExampleFeature() {
super(new MyBotCommand() {
@Override
public void runTextCommand(@NotNull MessageReceivedEvent event, @NotNull MyBotCommandFeature feature) {
final ExampleFeature self = ((ExampleFeature) feature);
// Use self to respond to the text command
}
@Override
public void runSlashCommand(@NotNull SlashCommandInteractionEvent event, @NotNull MyBotCommandFeature feature) {
final ExampleFeature self = ((ExampleFeature) feature);
// Use self to respond to the slash command
}
});
}
I was hoping I'd be able to make MyBotCommand
be parameterized with the generic type <F extends MyBotCommandFeature>
. Then instead of the abstract functions taking MyBotCommandFeature
as parameters, they'd take F
.
public abstract class MyBotCommand<F extends MyBotCommandFeature> {
public abstract void runTextCommand(@NotNull MessageReceivedEvent event, @NotNull F feature);
public abstract void runSlashCommand(@NotNull SlashCommandInteractionEvent event, @NotNull F feature);
}
However, I'm not sure I would then declare the type of the set of commands (defined in MyBotCommandFeature
, not individually in each feature) to say that the generic type F is the same as the class the set of commands is being stored in. I would want to be able to do something like this.
public class MyBotCommandFeature extends MyBotFeature {
@NotNull public final Set<MyBotCommand<this>> commands;
public MyBotCommandFeature(@NotNull MyBotCommand<this> @NotNull ... commands) {
if (commands.length == 0) {
this.commands = new HashSet<>();
} else {
this.commands = stream(commands).collect(Collectors.toSet());
}
}
@Nullable
@CheckForNull
@CheckReturnValue
public Set<MyBotCommand<this>> getExtraCommands(@NotNull Guild guild) {
return null;
}
}
public final class ExampleFeature extends MyBotCommandFeature {
public ExampleFeature() {
super(new MyBotCommand<ExampleFeature>() {
@Override
public void runTextCommand(@NotNull MessageReceivedEvent event, @NotNull ExampleFeature feature) {
// Use feature to respond to the text command
}
@Override
public void runSlashCommand(@NotNull SlashCommandInteractionEvent event, @NotNull ExampleFeature feature) {
// Use feature to respond to the text command
}
});
}
I have tried using a wildcard '?' instead of 'this' but then I'm stuck on what to pass to the abstract methods after matching a JDA event to a MyBotCommand. For example, I have this for slash commands, but I am unable to pass MyBotCommandFeature to MyBotCommand::runSlashCommand even though the MyBotCommand instance is made in the MyBotCommandFeature I am passing.
@Override
public void onSlashCommandInteraction(@NotNull SlashCommandInteractionEvent event) {
for (MyBotCommandFeature feature : features) {
for (MyBotCommand<?> command : feature.commands) {
if (command.name.equals(event.getName())) {
command.runSlashCommand(event,feature);
return;
}
}
}
}
Is there some way I can do something like this? If not, is there an alternative way to avoid having to cast MyBotCommandFeature
to an individual feature in most of my MyBotCommand
implementations?
CodePudding user response:
How about something like this? There is a type variable on MyBotCommandFeature
indicating the type argument of the MyBotCommand
that its commands are. Then the subclass ExampleFeature
can extend MyBotCommandFeature
with itself as the type argument. Of course, someone could write a class FeatureA extends MyBotCommandFeature<FeatureB>
, but everything is still type-safe and should compile without any warnings or casts, at least with what you've shown so far.
public abstract class MyBotCommand<F> {
public abstract void runTextCommand(@NotNull MessageReceivedEvent event, @NotNull F feature);
public abstract void runSlashCommand(@NotNull SlashCommandInteractionEvent event, @NotNull F feature);
}
public class MyBotCommandFeature<F> extends MyBotFeature {
@NotNull public final Set<MyBotCommand<F>> commands;
public MyBotCommandFeature(@NotNull MyBotCommand<F> @NotNull ... commands) {
if (commands.length == 0) {
this.commands = new HashSet<>();
} else {
this.commands = stream(commands).collect(Collectors.toSet());
}
}
@Nullable
@CheckForNull
@CheckReturnValue
public Set<MyBotCommand<F>> getExtraCommands(@NotNull Guild guild) {
return null;
}
}
public final class ExampleFeature extends MyBotCommandFeature<ExampleFeature> {
public ExampleFeature() {
super(new MyBotCommand<ExampleFeature>() {
// ...
});
}
}