Skip to content

Commit c4f08b6

Browse files
committed
Add subcommand support
1 parent 46317a8 commit c4f08b6

File tree

7 files changed

+133
-14
lines changed

7 files changed

+133
-14
lines changed

src/main/java/fr/zcraft/quartzlib/components/commands/CommandEndpoint.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ class CommandEndpoint extends CommandNode {
1313
}
1414

1515
@Override
16-
void run(Object instance, CommandSender sender, String[] args) throws CommandException {
17-
this.methods.get(0).run(instance, sender, args);
16+
void run(Object parentInstance, CommandSender sender, String[] args) throws CommandException {
17+
this.methods.get(0).run(parentInstance, sender, args);
1818
}
1919

2020
void addMethod(CommandMethod method) {

src/main/java/fr/zcraft/quartzlib/components/commands/CommandGroup.java

+54-7
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,55 @@
11
package fr.zcraft.quartzlib.components.commands;
22

33
import fr.zcraft.quartzlib.components.commands.exceptions.CommandException;
4+
import java.lang.reflect.Field;
45
import java.util.Arrays;
56
import java.util.HashMap;
67
import java.util.Map;
78
import java.util.function.Supplier;
89
import org.bukkit.command.CommandSender;
10+
import org.jetbrains.annotations.Nullable;
911

1012
class CommandGroup extends CommandNode {
1113
private final Class<?> commandGroupClass;
14+
15+
@Nullable
1216
private final Supplier<?> classInstanceSupplier;
17+
@Nullable
18+
private final GroupClassInstanceSupplier groupClassInstanceSupplier;
1319

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

16-
public CommandGroup(Class<?> commandGroupClass, Supplier<?> classInstanceSupplier, String name,
17-
TypeCollection typeCollection) {
18-
this(commandGroupClass, classInstanceSupplier, name, typeCollection, null);
22+
CommandGroup(Class<?> commandGroupClass, Supplier<?> classInstanceSupplier, String name,
23+
TypeCollection typeCollection) {
24+
this(commandGroupClass, classInstanceSupplier, null, name, typeCollection, null);
25+
}
26+
27+
CommandGroup(Class<?> commandGroupClass, GroupClassInstanceSupplier classInstanceSupplier, String name,
28+
CommandGroup parent, TypeCollection typeCollection) {
29+
this(commandGroupClass, null, classInstanceSupplier, name, typeCollection, parent);
30+
}
31+
32+
CommandGroup(CommandGroup parent, Field backingField, TypeCollection typeCollection) {
33+
this(
34+
backingField.getType(),
35+
GroupClassInstanceSupplier.backingField(backingField),
36+
backingField.getName(),
37+
parent,
38+
typeCollection
39+
);
1940
}
2041

21-
public CommandGroup(Class<?> commandGroupClass, Supplier<?> classInstanceSupplier, String name,
22-
TypeCollection typeCollection, CommandGroup parent) {
42+
private CommandGroup(
43+
Class<?> commandGroupClass,
44+
@Nullable Supplier<?> classInstanceSupplier,
45+
@Nullable GroupClassInstanceSupplier groupClassInstanceSupplier, String name,
46+
TypeCollection typeCollection, CommandGroup parent) {
2347
super(name, parent);
2448
this.commandGroupClass = commandGroupClass;
2549
this.classInstanceSupplier = classInstanceSupplier;
50+
this.groupClassInstanceSupplier = groupClassInstanceSupplier;
2651
DiscoveryUtils.getCommandMethods(commandGroupClass, typeCollection).forEach(this::addMethod);
52+
DiscoveryUtils.getSubCommands(this, typeCollection).forEach(this::addSubCommand);
2753
}
2854

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

70+
private void addSubCommand(CommandGroup commandGroup) {
71+
subCommands.put(commandGroup.getName(), commandGroup);
72+
}
73+
4474
void run(CommandSender sender, String... args) throws CommandException {
75+
if (classInstanceSupplier == null) {
76+
throw new IllegalStateException("This command group comes from a parent and cannot instanciate itself.");
77+
}
78+
4579
Object commandObject = classInstanceSupplier.get();
46-
run(commandObject, sender, args);
80+
runSelf(commandObject, sender, args);
4781
}
4882

4983
@Override
50-
void run(Object instance, CommandSender sender, String[] args) throws CommandException {
84+
void run(Object parentInstance, CommandSender sender, String[] args) throws CommandException {
85+
if (this.groupClassInstanceSupplier == null) {
86+
throw new IllegalStateException("This command group cannot be ran from a parent");
87+
}
88+
89+
Object instance = this.groupClassInstanceSupplier.supply(parentInstance);
90+
runSelf(instance, sender, args);
91+
}
92+
93+
private void runSelf(Object instance, CommandSender sender, String[] args) throws CommandException {
5194
String commandName = args[0];
5295
CommandNode subCommand = subCommands.get(commandName);
5396
subCommand.run(instance, sender, Arrays.copyOfRange(args, 1, args.length));
5497
}
98+
99+
public Class<?> getCommandGroupClass() {
100+
return commandGroupClass;
101+
}
55102
}

src/main/java/fr/zcraft/quartzlib/components/commands/CommandNode.java

+5-4
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,13 @@
22

33
import fr.zcraft.quartzlib.components.commands.exceptions.CommandException;
44
import org.bukkit.command.CommandSender;
5+
import org.jetbrains.annotations.Nullable;
56

67
abstract class CommandNode {
78
private final String name;
8-
private final CommandGroup parent;
9+
@Nullable private final CommandGroup parent;
910

10-
protected CommandNode(String name, CommandGroup parent) {
11+
protected CommandNode(String name, @Nullable CommandGroup parent) {
1112
this.name = name;
1213
this.parent = parent;
1314
}
@@ -16,9 +17,9 @@ public String getName() {
1617
return name;
1718
}
1819

19-
public CommandGroup getParent() {
20+
@Nullable public CommandGroup getParent() {
2021
return parent;
2122
}
2223

23-
abstract void run(Object instance, CommandSender sender, String[] args) throws CommandException;
24+
abstract void run(Object parentInstance, CommandSender sender, String[] args) throws CommandException;
2425
}

src/main/java/fr/zcraft/quartzlib/components/commands/DiscoveryUtils.java

+18-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package fr.zcraft.quartzlib.components.commands;
22

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

20+
public static Stream<CommandGroup> getSubCommands(CommandGroup commandGroup, TypeCollection typeCollection) {
21+
return Arrays.stream(commandGroup.getCommandGroupClass().getDeclaredFields())
22+
.filter(m -> hasRunnableModifiers(m.getModifiers()))
23+
.filter(DiscoveryUtils::isSubcommand)
24+
.map(field -> new CommandGroup(commandGroup, field, typeCollection));
25+
}
26+
27+
private static boolean isSubcommand(AccessibleObject field) {
28+
return field.isAnnotationPresent(SubCommand.class);
29+
}
30+
1831
public static Supplier<?> getClassConstructorSupplier(Class<?> commandGroupClass) {
1932
Constructor<?> constructor = commandGroupClass.getDeclaredConstructors()[0];
2033
return () -> {
@@ -25,4 +38,8 @@ public static Supplier<?> getClassConstructorSupplier(Class<?> commandGroupClass
2538
}
2639
};
2740
}
41+
42+
private static boolean hasRunnableModifiers(int modifiers) {
43+
return Modifier.isPublic(modifiers) && !Modifier.isStatic(modifiers);
44+
}
2845
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package fr.zcraft.quartzlib.components.commands;
2+
3+
import java.lang.reflect.Field;
4+
5+
public interface GroupClassInstanceSupplier {
6+
static GroupClassInstanceSupplier backingField(Field field) {
7+
return (instance) -> {
8+
try {
9+
return field.get(instance);
10+
} catch (IllegalAccessException e) {
11+
throw new RuntimeException(e); // TODO
12+
}
13+
};
14+
}
15+
16+
Object supply(Object parent);
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package fr.zcraft.quartzlib.components.commands.attributes;
2+
3+
import java.lang.annotation.ElementType;
4+
import java.lang.annotation.Retention;
5+
import java.lang.annotation.RetentionPolicy;
6+
import java.lang.annotation.Target;
7+
8+
@Retention(RetentionPolicy.RUNTIME)
9+
@Target({ElementType.FIELD, ElementType.METHOD})
10+
public @interface SubCommand {
11+
}

src/test/java/fr/zcraft/quartzlib/components/commands/CommandGraphTests.java

+26
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import fr.zcraft.quartzlib.MockedBukkitTest;
44
import fr.zcraft.quartzlib.components.commands.attributes.Sender;
5+
import fr.zcraft.quartzlib.components.commands.attributes.SubCommand;
56
import fr.zcraft.quartzlib.components.commands.exceptions.CommandException;
67
import java.util.stream.StreamSupport;
78
import org.bukkit.command.CommandSender;
@@ -135,6 +136,31 @@ public void add(@Sender CommandSender sender) {
135136
Assert.assertArrayEquals(new CommandSender[] {player}, senders);
136137
}
137138

139+
@Test
140+
public void canCallSubcommand() throws CommandException {
141+
final boolean[] ran = {false};
142+
143+
class SubFooCommand {
144+
public void add() {
145+
ran[0] = true;
146+
}
147+
}
148+
149+
class FooCommand {
150+
@SubCommand
151+
public final SubFooCommand sub = new SubFooCommand();
152+
153+
public void add() {
154+
throw new RuntimeException("This shouldn't run!");
155+
}
156+
}
157+
158+
Player player = server.addPlayer();
159+
commands.registerCommand("foo", FooCommand.class, () -> new FooCommand());
160+
commands.run(player, "foo", "sub", "add");
161+
Assert.assertArrayEquals(new boolean[] {true}, ran);
162+
}
163+
138164
enum FooEnum {
139165
FOO, BAR
140166
}

0 commit comments

Comments
 (0)