Skip to content

Commit a14bc55

Browse files
committed
Can now parse all enums by default, and add infrastructure to parse any generic type
1 parent 4a12992 commit a14bc55

16 files changed

+170
-23
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package fr.zcraft.quartzlib.components.commands;
22

3+
import fr.zcraft.quartzlib.components.commands.exceptions.ArgumentParseException;
4+
35
@FunctionalInterface
46
public interface ArgumentType<T> {
5-
T parse(String raw);
7+
T parse(String raw) throws ArgumentParseException;
68
}

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

+1-3
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
package fr.zcraft.quartzlib.components.commands;
22

3-
import fr.zcraft.quartzlib.components.commands.ArgumentType;
4-
5-
public class ArgumentTypeHandler<T> {
3+
class ArgumentTypeHandler<T> {
64
private final Class<T> resultType;
75
private final ArgumentType<T> typeHandler;
86

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

3-
import fr.zcraft.quartzlib.components.commands.arguments.primitive.IntegerTypeHandler;
3+
import fr.zcraft.quartzlib.components.commands.arguments.generic.EnumArgumentType;
4+
import fr.zcraft.quartzlib.components.commands.arguments.primitive.IntegerArgumentType;
5+
import org.jetbrains.annotations.Nullable;
46

5-
import java.util.HashMap;
6-
import java.util.Map;
7-
import java.util.Optional;
7+
import java.util.*;
88

99
class ArgumentTypeHandlerCollection {
1010
private final Map<Class<?>, ArgumentTypeHandler<?>> argumentTypeHandlerMap = new HashMap<>();
11+
private final List<GenericArgumentType<?>> genericArgumentTypes = new ArrayList<>();
1112

1213
public ArgumentTypeHandlerCollection () {
1314
this.registerNativeTypes();
@@ -18,13 +19,34 @@ public <T> void register(ArgumentTypeHandler<T> typeHandler)
1819
argumentTypeHandlerMap.put(typeHandler.getResultType(), typeHandler);
1920
}
2021

22+
public <T> void register(GenericArgumentType<T> genericArgumentType) {
23+
genericArgumentTypes.add(genericArgumentType);
24+
}
25+
2126
public Optional<ArgumentTypeHandler<?>> findTypeHandler(Class<?> resultType) {
22-
return Optional.ofNullable(argumentTypeHandlerMap.get(resultType));
27+
ArgumentTypeHandler<?> typeHandler = argumentTypeHandlerMap.get(resultType);
28+
if (typeHandler != null) return Optional.of(typeHandler);
29+
return this.findGenericTypeHandler(resultType);
2330
}
2431

25-
private void registerNativeTypes () {
26-
register(new ArgumentTypeHandler<>(Integer.class, new IntegerTypeHandler()));
32+
private <T> Optional<ArgumentTypeHandler<?>> findGenericTypeHandler(Class<T> resultType) {
33+
for (GenericArgumentType<?> t : genericArgumentTypes) {
34+
Optional<? extends ArgumentType<?>> matchingArgumentType = t.getMatchingArgumentType(resultType);
2735

36+
if (matchingArgumentType.isPresent()) {
37+
ArgumentTypeHandler<?> typeHandler = new ArgumentTypeHandler<>(resultType, (ArgumentType<T>) matchingArgumentType.get());
38+
return Optional.of(typeHandler);
39+
}
40+
}
41+
return Optional.empty();
42+
}
43+
44+
private void registerNativeTypes () {
45+
// Primitive types
46+
register(new ArgumentTypeHandler<>(Integer.class, new IntegerArgumentType()));
2847
register(new ArgumentTypeHandler<>(String.class, s -> s));
48+
49+
// Generic types
50+
register(new EnumArgumentType());
2951
}
3052
}

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

+3-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.exceptions.CommandException;
4+
35
import java.util.ArrayList;
46
import java.util.List;
57

@@ -11,7 +13,7 @@ class CommandEndpoint extends CommandNode {
1113
}
1214

1315
@Override
14-
void run(Object instance, String[] args) {
16+
void run(Object instance, String[] args) throws CommandException {
1517
this.methods.get(0).run(instance, args);
1618
}
1719

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

+4-2
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.exceptions.CommandException;
4+
35
import java.util.Arrays;
46
import java.util.HashMap;
57
import java.util.Map;
@@ -37,13 +39,13 @@ private void addMethod(CommandMethod method) {
3739
endpoint.addMethod(method);
3840
}
3941

40-
void run(String... args) {
42+
void run(String... args) throws CommandException {
4143
Object commandObject = classInstanceSupplier.get();
4244
run(commandObject, args);
4345
}
4446

4547
@Override
46-
void run(Object instance, String[] args) {
48+
void run(Object instance, String[] args) throws CommandException {
4749
String commandName = args[0];
4850
CommandNode subCommand = subCommands.get(commandName);
4951
subCommand.run(instance, Arrays.copyOfRange(args, 1, args.length));

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

+3-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.exceptions.CommandException;
4+
35
import java.util.HashMap;
46
import java.util.Map;
57
import java.util.function.Supplier;
@@ -13,7 +15,7 @@ public <T> void registerCommand(String name, Class<T> commandType, Supplier<T> c
1315
rootCommands.put(name, group);
1416
}
1517

16-
public void run(String commandName, String... args) {
18+
public void run(String commandName, String... args) throws CommandException {
1719
((CommandGroup) rootCommands.get(commandName)).run(args); // TODO
1820
}
1921
}

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

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

3+
import fr.zcraft.quartzlib.components.commands.exceptions.ArgumentParseException;
4+
import fr.zcraft.quartzlib.components.commands.exceptions.CommandException;
5+
36
import java.lang.reflect.InvocationTargetException;
47
import java.lang.reflect.Method;
58
import java.util.Arrays;
@@ -22,7 +25,7 @@ public String getName() {
2225
return name;
2326
}
2427

25-
public void run(Object target, String[] args) {
28+
public void run(Object target, String[] args) throws CommandException {
2629
Object[] parsedArgs = parseArguments(args);
2730
try {
2831
this.method.invoke(target, parsedArgs);
@@ -31,7 +34,7 @@ public void run(Object target, String[] args) {
3134
}
3235
}
3336

34-
private Object[] parseArguments(String[] args) {
37+
private Object[] parseArguments(String[] args) throws ArgumentParseException {
3538
Object[] parsed = new Object[args.length];
3639

3740
for (int i = 0; i < args.length; i++) {

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

+3-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.exceptions.ArgumentParseException;
4+
35
import java.lang.reflect.Parameter;
46

57
public class CommandMethodArgument {
@@ -11,7 +13,7 @@ public CommandMethodArgument(Parameter parameter, ArgumentTypeHandlerCollection
1113
this.typeHandler = typeHandlerCollection.findTypeHandler(parameter.getType()).get(); // FIXME: handle unknown types
1214
}
1315

14-
public Object parse(String raw) {
16+
public Object parse(String raw) throws ArgumentParseException {
1517
return this.typeHandler.getTypeHandler().parse(raw);
1618
}
1719
}

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

+3-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.exceptions.CommandException;
4+
35
abstract class CommandNode {
46
private final String name;
57
private final CommandGroup parent;
@@ -17,5 +19,5 @@ public CommandGroup getParent() {
1719
return parent;
1820
}
1921

20-
abstract void run(Object instance, String[] args);
22+
abstract void run(Object instance, String[] args) throws CommandException;
2123
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package fr.zcraft.quartzlib.components.commands;
2+
3+
import java.util.Optional;
4+
5+
public interface GenericArgumentType<T> {
6+
Optional<ArgumentType<T>> getMatchingArgumentType(Class<?> type);
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package fr.zcraft.quartzlib.components.commands.arguments.generic;
2+
3+
import fr.zcraft.quartzlib.components.commands.ArgumentType;
4+
import fr.zcraft.quartzlib.components.commands.GenericArgumentType;
5+
import fr.zcraft.quartzlib.components.commands.exceptions.ArgumentParseException;
6+
7+
import java.lang.reflect.Modifier;
8+
import java.util.Arrays;
9+
import java.util.HashMap;
10+
import java.util.Map;
11+
import java.util.Optional;
12+
13+
public class EnumArgumentType implements GenericArgumentType<Enum<?>> {
14+
@Override
15+
public Optional<ArgumentType<Enum<?>>> getMatchingArgumentType(Class<?> type) {
16+
if (type.isEnum()) {
17+
return Optional.of(new DiscreteEnumArgumentType(type));
18+
}
19+
return Optional.empty();
20+
}
21+
22+
static private class DiscreteEnumArgumentType implements ArgumentType<Enum<?>> {
23+
private final Map<String, Enum<?>> enumValues;
24+
25+
public DiscreteEnumArgumentType(Class<?> enumClass) {
26+
enumValues = getEnumValues(enumClass);
27+
}
28+
29+
@Override
30+
public Enum<?> parse(String raw) throws ArgumentParseException {
31+
Enum<?> value = enumValues.get(raw);
32+
if (value == null) throw new EnumParseException();
33+
return value;
34+
}
35+
}
36+
37+
static private class EnumParseException extends ArgumentParseException {
38+
39+
}
40+
41+
static private Map<String, Enum<?>> getEnumValues (Class<?> enumClass) {
42+
Map<String, Enum<?>> enumValues = new HashMap<>();
43+
44+
Arrays.stream(enumClass.getDeclaredFields())
45+
.filter(f -> Modifier.isPublic(f.getModifiers())
46+
&& Modifier.isStatic(f.getModifiers())
47+
&& enumClass.isAssignableFrom(f.getType()))
48+
.forEach(f -> {
49+
try {
50+
f.setAccessible(true);
51+
enumValues.put(f.getName().toLowerCase(), (Enum<?>)f.get(null));
52+
} catch (IllegalAccessException e) {
53+
throw new RuntimeException(e);
54+
}
55+
});
56+
57+
return enumValues;
58+
}
59+
}

src/main/java/fr/zcraft/quartzlib/components/commands/arguments/primitive/IntegerTypeHandler.java renamed to src/main/java/fr/zcraft/quartzlib/components/commands/arguments/primitive/IntegerArgumentType.java

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

33
import fr.zcraft.quartzlib.components.commands.ArgumentType;
44

5-
public class IntegerTypeHandler implements ArgumentType<Integer> {
5+
public class IntegerArgumentType implements ArgumentType<Integer> {
66
@Override
77
public Integer parse(String raw) {
88
return Integer.parseInt(raw, 10); // TODO: handle exceptions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
package fr.zcraft.quartzlib.components.commands.exceptions;
2+
3+
public class ArgumentParseException extends CommandException {
4+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
package fr.zcraft.quartzlib.components.commands.exceptions;
2+
3+
public abstract class CommandException extends Exception {
4+
}

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

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

3+
import fr.zcraft.quartzlib.components.commands.exceptions.CommandException;
34
import org.junit.jupiter.api.Assertions;
45
import org.junit.jupiter.api.BeforeEach;
56
import org.junit.jupiter.api.Test;
@@ -42,7 +43,7 @@ public void list () {}
4243
Assertions.assertArrayEquals(new String[] {"add", "delete"}, commandNames);
4344
}
4445

45-
@Test public void canRunBasicSubcommands() {
46+
@Test public void canRunBasicSubcommands() throws CommandException {
4647
final boolean[] ran = {false, false, false};
4748

4849
class FooCommand {
@@ -56,7 +57,7 @@ class FooCommand {
5657
Assertions.assertArrayEquals(new boolean[] { false, true, false }, ran);
5758
}
5859

59-
@Test public void canReceiveStringArguments() {
60+
@Test public void canReceiveStringArguments() throws CommandException {
6061
final String[] argValue = {""};
6162

6263
class FooCommand {
@@ -68,7 +69,7 @@ class FooCommand {
6869
Assertions.assertArrayEquals(new String[] { "pomf" }, argValue);
6970
}
7071

71-
@Test public void canReceiveParsedArguments() {
72+
@Test public void canReceiveParsedArguments() throws CommandException {
7273
final int[] argValue = {0};
7374

7475
class FooCommand {
@@ -79,4 +80,19 @@ class FooCommand {
7980
commands.run("foo", "add", "42");
8081
Assertions.assertArrayEquals(new int[] { 42 }, argValue);
8182
}
83+
84+
enum FooEnum { FOO, BAR }
85+
@Test public void canReceiveEnumArguments() throws CommandException {
86+
final FooEnum[] argValue = {null};
87+
88+
class FooCommand {
89+
public void add (FooEnum arg) { argValue[0] = arg; }
90+
}
91+
92+
commands.registerCommand("foo", FooCommand.class, () -> new FooCommand());
93+
commands.run("foo", "add", "foo");
94+
Assertions.assertArrayEquals(new FooEnum[] { FooEnum.FOO }, argValue);
95+
commands.run("foo", "add", "bar");
96+
Assertions.assertArrayEquals(new FooEnum[] { FooEnum.BAR }, argValue);
97+
}
8298
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package fr.zcraft.quartzlib.components.commands.arguments.generic;
2+
3+
import fr.zcraft.quartzlib.components.commands.ArgumentType;
4+
import fr.zcraft.quartzlib.components.commands.exceptions.ArgumentParseException;
5+
import org.junit.jupiter.api.Assertions;
6+
import org.junit.jupiter.api.Test;
7+
8+
public class EnumArgumentTypeTests {
9+
private final EnumArgumentType enumArgumentType = new EnumArgumentType();
10+
11+
private enum SimpleEnum { FOO, BAR }
12+
13+
@Test
14+
public void worksOnSimpleEnum() throws ArgumentParseException {
15+
ArgumentType<?> argumentType = enumArgumentType.getMatchingArgumentType(SimpleEnum.class).get();
16+
17+
Assertions.assertEquals(SimpleEnum.FOO, argumentType.parse("foo"));
18+
Assertions.assertEquals(SimpleEnum.BAR, argumentType.parse("bar"));
19+
20+
Assertions.assertThrows(ArgumentParseException.class, () -> argumentType.parse("blah"));
21+
}
22+
}

0 commit comments

Comments
 (0)