Skip to content

Commit

Permalink
Add subcommand support
Browse files Browse the repository at this point in the history
  • Loading branch information
prokopyl committed Dec 12, 2020
1 parent 5bd68e9 commit e47cd61
Show file tree
Hide file tree
Showing 7 changed files with 133 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ class CommandEndpoint extends CommandNode {
}

@Override
void run(Object instance, CommandSender sender, String[] args) throws CommandException {
this.methods.get(0).run(instance, sender, args);
void run(Object parentInstance, CommandSender sender, String[] args) throws CommandException {
this.methods.get(0).run(parentInstance, sender, args);
}

void addMethod(CommandMethod method) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,29 +1,55 @@
package fr.zcraft.quartzlib.components.commands;

import fr.zcraft.quartzlib.components.commands.exceptions.CommandException;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Supplier;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.Nullable;

class CommandGroup extends CommandNode {
private final Class<?> commandGroupClass;

@Nullable
private final Supplier<?> classInstanceSupplier;
@Nullable
private final GroupClassInstanceSupplier groupClassInstanceSupplier;

private final Map<String, CommandNode> subCommands = new HashMap<>();

public CommandGroup(Class<?> commandGroupClass, Supplier<?> classInstanceSupplier, String name,
TypeCollection typeCollection) {
this(commandGroupClass, classInstanceSupplier, name, typeCollection, null);
CommandGroup(Class<?> commandGroupClass, Supplier<?> classInstanceSupplier, String name,
TypeCollection typeCollection) {
this(commandGroupClass, classInstanceSupplier, null, name, typeCollection, null);
}

CommandGroup(Class<?> commandGroupClass, GroupClassInstanceSupplier classInstanceSupplier, String name,
CommandGroup parent, TypeCollection typeCollection) {
this(commandGroupClass, null, classInstanceSupplier, name, typeCollection, parent);
}

CommandGroup(CommandGroup parent, Field backingField, TypeCollection typeCollection) {
this(
backingField.getType(),
GroupClassInstanceSupplier.backingField(backingField),
backingField.getName(),
parent,
typeCollection
);
}

public CommandGroup(Class<?> commandGroupClass, Supplier<?> classInstanceSupplier, String name,
TypeCollection typeCollection, CommandGroup parent) {
private CommandGroup(
Class<?> commandGroupClass,
@Nullable Supplier<?> classInstanceSupplier,
@Nullable GroupClassInstanceSupplier groupClassInstanceSupplier, String name,
TypeCollection typeCollection, CommandGroup parent) {
super(name, parent);
this.commandGroupClass = commandGroupClass;
this.classInstanceSupplier = classInstanceSupplier;
this.groupClassInstanceSupplier = groupClassInstanceSupplier;
DiscoveryUtils.getCommandMethods(commandGroupClass, typeCollection).forEach(this::addMethod);
DiscoveryUtils.getSubCommands(this, typeCollection).forEach(this::addSubCommand);
}

public Iterable<CommandNode> getSubCommands() {
Expand All @@ -41,15 +67,36 @@ private void addMethod(CommandMethod method) {
endpoint.addMethod(method);
}

private void addSubCommand(CommandGroup commandGroup) {
subCommands.put(commandGroup.getName(), commandGroup);
}

void run(CommandSender sender, String... args) throws CommandException {
if (classInstanceSupplier == null) {
throw new IllegalStateException("This command group comes from a parent and cannot instanciate itself.");
}

Object commandObject = classInstanceSupplier.get();
run(commandObject, sender, args);
runSelf(commandObject, sender, args);
}

@Override
void run(Object instance, CommandSender sender, String[] args) throws CommandException {
void run(Object parentInstance, CommandSender sender, String[] args) throws CommandException {
if (this.groupClassInstanceSupplier == null) {
throw new IllegalStateException("This command group cannot be ran from a parent");
}

Object instance = this.groupClassInstanceSupplier.supply(parentInstance);
runSelf(instance, sender, args);
}

private void runSelf(Object instance, CommandSender sender, String[] args) throws CommandException {
String commandName = args[0];
CommandNode subCommand = subCommands.get(commandName);
subCommand.run(instance, sender, Arrays.copyOfRange(args, 1, args.length));
}

public Class<?> getCommandGroupClass() {
return commandGroupClass;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@

import fr.zcraft.quartzlib.components.commands.exceptions.CommandException;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.Nullable;

abstract class CommandNode {
private final String name;
private final CommandGroup parent;
@Nullable private final CommandGroup parent;

protected CommandNode(String name, CommandGroup parent) {
protected CommandNode(String name, @Nullable CommandGroup parent) {
this.name = name;
this.parent = parent;
}
Expand All @@ -16,9 +17,9 @@ public String getName() {
return name;
}

public CommandGroup getParent() {
@Nullable public CommandGroup getParent() {
return parent;
}

abstract void run(Object instance, CommandSender sender, String[] args) throws CommandException;
abstract void run(Object parentInstance, CommandSender sender, String[] args) throws CommandException;
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package fr.zcraft.quartzlib.components.commands;

import fr.zcraft.quartzlib.components.commands.attributes.SubCommand;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
Expand All @@ -11,10 +13,21 @@
abstract class DiscoveryUtils {
public static Stream<CommandMethod> getCommandMethods(Class<?> commandGroupClass, TypeCollection typeCollection) {
return Arrays.stream(commandGroupClass.getDeclaredMethods())
.filter(m -> Modifier.isPublic(m.getModifiers()) && !Modifier.isStatic(m.getModifiers()))
.filter(m -> hasRunnableModifiers(m.getModifiers()))
.map((Method method) -> new CommandMethod(method, typeCollection));
}

public static Stream<CommandGroup> getSubCommands(CommandGroup commandGroup, TypeCollection typeCollection) {
return Arrays.stream(commandGroup.getCommandGroupClass().getDeclaredFields())
.filter(m -> hasRunnableModifiers(m.getModifiers()))
.filter(DiscoveryUtils::isSubcommand)
.map(field -> new CommandGroup(commandGroup, field, typeCollection));
}

private static boolean isSubcommand(AccessibleObject field) {
return field.isAnnotationPresent(SubCommand.class);
}

public static Supplier<?> getClassConstructorSupplier(Class<?> commandGroupClass) {
Constructor<?> constructor = commandGroupClass.getDeclaredConstructors()[0];
return () -> {
Expand All @@ -25,4 +38,8 @@ public static Supplier<?> getClassConstructorSupplier(Class<?> commandGroupClass
}
};
}

private static boolean hasRunnableModifiers(int modifiers) {
return Modifier.isPublic(modifiers) && !Modifier.isStatic(modifiers);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package fr.zcraft.quartzlib.components.commands;

import java.lang.reflect.Field;

public interface GroupClassInstanceSupplier {
static GroupClassInstanceSupplier backingField(Field field) {
return (instance) -> {
try {
return field.get(instance);
} catch (IllegalAccessException e) {
throw new RuntimeException(e); // TODO
}
};
}

Object supply(Object parent);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package fr.zcraft.quartzlib.components.commands.attributes;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.METHOD})
public @interface SubCommand {
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import fr.zcraft.quartzlib.MockedBukkitTest;
import fr.zcraft.quartzlib.components.commands.attributes.Sender;
import fr.zcraft.quartzlib.components.commands.attributes.SubCommand;
import fr.zcraft.quartzlib.components.commands.exceptions.CommandException;
import java.util.stream.StreamSupport;
import org.bukkit.command.CommandSender;
Expand Down Expand Up @@ -135,6 +136,31 @@ public void add(@Sender CommandSender sender) {
Assert.assertArrayEquals(new CommandSender[] {player}, senders);
}

@Test
public void canCallSubcommand() throws CommandException {
final boolean[] ran = {false};

class SubFooCommand {
public void add() {
ran[0] = true;
}
}

class FooCommand {
@SubCommand
public final SubFooCommand sub = new SubFooCommand();

public void add() {
throw new RuntimeException("This shouldn't run!");
}
}

Player player = server.addPlayer();
commands.registerCommand("foo", FooCommand.class, () -> new FooCommand());
commands.run(player, "foo", "sub", "add");
Assert.assertArrayEquals(new boolean[] {true}, ran);
}

enum FooEnum {
FOO, BAR
}
Expand Down

0 comments on commit e47cd61

Please sign in to comment.