Skip to content

Commit

Permalink
Can now parse all enums by default, and add infrastructure to parse a…
Browse files Browse the repository at this point in the history
…ny generic type
  • Loading branch information
prokopyl committed Nov 24, 2020
1 parent 4a12992 commit a14bc55
Show file tree
Hide file tree
Showing 16 changed files with 170 additions and 23 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package fr.zcraft.quartzlib.components.commands;

import fr.zcraft.quartzlib.components.commands.exceptions.ArgumentParseException;

@FunctionalInterface
public interface ArgumentType<T> {
T parse(String raw);
T parse(String raw) throws ArgumentParseException;
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package fr.zcraft.quartzlib.components.commands;

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

public class ArgumentTypeHandler<T> {
class ArgumentTypeHandler<T> {
private final Class<T> resultType;
private final ArgumentType<T> typeHandler;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
package fr.zcraft.quartzlib.components.commands;

import fr.zcraft.quartzlib.components.commands.arguments.primitive.IntegerTypeHandler;
import fr.zcraft.quartzlib.components.commands.arguments.generic.EnumArgumentType;
import fr.zcraft.quartzlib.components.commands.arguments.primitive.IntegerArgumentType;
import org.jetbrains.annotations.Nullable;

import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.*;

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

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

public <T> void register(GenericArgumentType<T> genericArgumentType) {
genericArgumentTypes.add(genericArgumentType);
}

public Optional<ArgumentTypeHandler<?>> findTypeHandler(Class<?> resultType) {
return Optional.ofNullable(argumentTypeHandlerMap.get(resultType));
ArgumentTypeHandler<?> typeHandler = argumentTypeHandlerMap.get(resultType);
if (typeHandler != null) return Optional.of(typeHandler);
return this.findGenericTypeHandler(resultType);
}

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

if (matchingArgumentType.isPresent()) {
ArgumentTypeHandler<?> typeHandler = new ArgumentTypeHandler<>(resultType, (ArgumentType<T>) matchingArgumentType.get());
return Optional.of(typeHandler);
}
}
return Optional.empty();
}

private void registerNativeTypes () {
// Primitive types
register(new ArgumentTypeHandler<>(Integer.class, new IntegerArgumentType()));
register(new ArgumentTypeHandler<>(String.class, s -> s));

// Generic types
register(new EnumArgumentType());
}
}
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.exceptions.CommandException;

import java.util.ArrayList;
import java.util.List;

Expand All @@ -11,7 +13,7 @@ class CommandEndpoint extends CommandNode {
}

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

Expand Down
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.exceptions.CommandException;

import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
Expand Down Expand Up @@ -37,13 +39,13 @@ private void addMethod(CommandMethod method) {
endpoint.addMethod(method);
}

void run(String... args) {
void run(String... args) throws CommandException {
Object commandObject = classInstanceSupplier.get();
run(commandObject, args);
}

@Override
void run(Object instance, String[] args) {
void run(Object instance, String[] args) throws CommandException {
String commandName = args[0];
CommandNode subCommand = subCommands.get(commandName);
subCommand.run(instance, Arrays.copyOfRange(args, 1, args.length));
Expand Down
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.exceptions.CommandException;

import java.util.HashMap;
import java.util.Map;
import java.util.function.Supplier;
Expand All @@ -13,7 +15,7 @@ public <T> void registerCommand(String name, Class<T> commandType, Supplier<T> c
rootCommands.put(name, group);
}

public void run(String commandName, String... args) {
public void run(String commandName, String... args) throws CommandException {
((CommandGroup) rootCommands.get(commandName)).run(args); // TODO
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package fr.zcraft.quartzlib.components.commands;

import fr.zcraft.quartzlib.components.commands.exceptions.ArgumentParseException;
import fr.zcraft.quartzlib.components.commands.exceptions.CommandException;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
Expand All @@ -22,7 +25,7 @@ public String getName() {
return name;
}

public void run(Object target, String[] args) {
public void run(Object target, String[] args) throws CommandException {
Object[] parsedArgs = parseArguments(args);
try {
this.method.invoke(target, parsedArgs);
Expand All @@ -31,7 +34,7 @@ public void run(Object target, String[] args) {
}
}

private Object[] parseArguments(String[] args) {
private Object[] parseArguments(String[] args) throws ArgumentParseException {
Object[] parsed = new Object[args.length];

for (int i = 0; i < args.length; i++) {
Expand Down
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.exceptions.ArgumentParseException;

import java.lang.reflect.Parameter;

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

public Object parse(String raw) {
public Object parse(String raw) throws ArgumentParseException {
return this.typeHandler.getTypeHandler().parse(raw);
}
}
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.exceptions.CommandException;

abstract class CommandNode {
private final String name;
private final CommandGroup parent;
Expand All @@ -17,5 +19,5 @@ public CommandGroup getParent() {
return parent;
}

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

import java.util.Optional;

public interface GenericArgumentType<T> {
Optional<ArgumentType<T>> getMatchingArgumentType(Class<?> type);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package fr.zcraft.quartzlib.components.commands.arguments.generic;

import fr.zcraft.quartzlib.components.commands.ArgumentType;
import fr.zcraft.quartzlib.components.commands.GenericArgumentType;
import fr.zcraft.quartzlib.components.commands.exceptions.ArgumentParseException;

import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

public class EnumArgumentType implements GenericArgumentType<Enum<?>> {
@Override
public Optional<ArgumentType<Enum<?>>> getMatchingArgumentType(Class<?> type) {
if (type.isEnum()) {
return Optional.of(new DiscreteEnumArgumentType(type));
}
return Optional.empty();
}

static private class DiscreteEnumArgumentType implements ArgumentType<Enum<?>> {
private final Map<String, Enum<?>> enumValues;

public DiscreteEnumArgumentType(Class<?> enumClass) {
enumValues = getEnumValues(enumClass);
}

@Override
public Enum<?> parse(String raw) throws ArgumentParseException {
Enum<?> value = enumValues.get(raw);
if (value == null) throw new EnumParseException();
return value;
}
}

static private class EnumParseException extends ArgumentParseException {

}

static private Map<String, Enum<?>> getEnumValues (Class<?> enumClass) {
Map<String, Enum<?>> enumValues = new HashMap<>();

Arrays.stream(enumClass.getDeclaredFields())
.filter(f -> Modifier.isPublic(f.getModifiers())
&& Modifier.isStatic(f.getModifiers())
&& enumClass.isAssignableFrom(f.getType()))
.forEach(f -> {
try {
f.setAccessible(true);
enumValues.put(f.getName().toLowerCase(), (Enum<?>)f.get(null));
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
});

return enumValues;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

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

public class IntegerTypeHandler implements ArgumentType<Integer> {
public class IntegerArgumentType implements ArgumentType<Integer> {
@Override
public Integer parse(String raw) {
return Integer.parseInt(raw, 10); // TODO: handle exceptions
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package fr.zcraft.quartzlib.components.commands.exceptions;

public class ArgumentParseException extends CommandException {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package fr.zcraft.quartzlib.components.commands.exceptions;

public abstract class CommandException extends Exception {
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package fr.zcraft.quartzlib.components.commands;

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

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

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

@Test public void canReceiveStringArguments() {
@Test public void canReceiveStringArguments() throws CommandException {
final String[] argValue = {""};

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

@Test public void canReceiveParsedArguments() {
@Test public void canReceiveParsedArguments() throws CommandException {
final int[] argValue = {0};

class FooCommand {
Expand All @@ -79,4 +80,19 @@ class FooCommand {
commands.run("foo", "add", "42");
Assertions.assertArrayEquals(new int[] { 42 }, argValue);
}

enum FooEnum { FOO, BAR }
@Test public void canReceiveEnumArguments() throws CommandException {
final FooEnum[] argValue = {null};

class FooCommand {
public void add (FooEnum arg) { argValue[0] = arg; }
}

commands.registerCommand("foo", FooCommand.class, () -> new FooCommand());
commands.run("foo", "add", "foo");
Assertions.assertArrayEquals(new FooEnum[] { FooEnum.FOO }, argValue);
commands.run("foo", "add", "bar");
Assertions.assertArrayEquals(new FooEnum[] { FooEnum.BAR }, argValue);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package fr.zcraft.quartzlib.components.commands.arguments.generic;

import fr.zcraft.quartzlib.components.commands.ArgumentType;
import fr.zcraft.quartzlib.components.commands.exceptions.ArgumentParseException;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

public class EnumArgumentTypeTests {
private final EnumArgumentType enumArgumentType = new EnumArgumentType();

private enum SimpleEnum { FOO, BAR }

@Test
public void worksOnSimpleEnum() throws ArgumentParseException {
ArgumentType<?> argumentType = enumArgumentType.getMatchingArgumentType(SimpleEnum.class).get();

Assertions.assertEquals(SimpleEnum.FOO, argumentType.parse("foo"));
Assertions.assertEquals(SimpleEnum.BAR, argumentType.parse("bar"));

Assertions.assertThrows(ArgumentParseException.class, () -> argumentType.parse("blah"));
}
}

0 comments on commit a14bc55

Please sign in to comment.