Skip to content

Commit

Permalink
Add @Args annotation. Handle string argument validator. Add unit test…
Browse files Browse the repository at this point in the history
…s for @Args and String suggestion.
  • Loading branch information
Rollczi committed Aug 18, 2022
1 parent c75eb19 commit 7aa3fe8
Show file tree
Hide file tree
Showing 15 changed files with 174 additions and 34 deletions.
@@ -1,5 +1,6 @@
package dev.rollczi.litecommands;

import dev.rollczi.litecommands.argument.Args;
import dev.rollczi.litecommands.argument.Argument;
import dev.rollczi.litecommands.argument.simple.MultilevelArgument;
import dev.rollczi.litecommands.argument.simple.OneArgument;
Expand Down Expand Up @@ -53,7 +54,7 @@ public interface LiteCommandsBuilder<SENDER> {

LiteCommandsBuilder<SENDER> typeUnsafeBind(Class<?> type, TypeBind<?> typeBind);

<T, A extends Annotation> LiteCommandsBuilder<SENDER> annotatedBind(Class<T> type, Class<A> annotationType, AnnotationBind<T, A> annotationBind);
<T, A extends Annotation> LiteCommandsBuilder<SENDER> annotatedBind(Class<T> type, Class<A> annotationType, AnnotationBind<T, SENDER, A> annotationBind);

<T> LiteCommandsBuilder<SENDER> contextualBind(Class<T> on, Contextual<SENDER, T> contextual);

Expand Down
@@ -0,0 +1,11 @@
package dev.rollczi.litecommands.argument;

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

@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface Args {
}
Expand Up @@ -20,7 +20,7 @@ public class EnumArgument<SENDER> implements SingleArgument<SENDER, Arg>, Parame
@Override
public MatchResult match(LiteInvocation invocation, ArgumentContext<Arg> context, String argument) {
return EnumUtil.parse(context.parameter().getType(), argument)
.fold(MatchResult::matchedSingle, (exception) -> MatchResult.notMatched());
.fold(MatchResult::matchedSingle, exception -> MatchResult.notMatched());
}

@Override
Expand Down
Expand Up @@ -167,7 +167,7 @@ public LiteCommandsBuilder<SENDER> typeUnsafeBind(Class<?> type, TypeBind<?> typ
}

@Override
public <T, A extends Annotation> LiteCommandsBuilder<SENDER> annotatedBind(Class<T> type, Class<A> annotationType, AnnotationBind<T, A> annotationBind) {
public <T, A extends Annotation> LiteCommandsBuilder<SENDER> annotatedBind(Class<T> type, Class<A> annotationType, AnnotationBind<T, SENDER, A> annotationBind) {
this.injectorSettings.annotationBind(type, annotationType, annotationBind);
return this;
}
Expand Down
Expand Up @@ -2,6 +2,7 @@

import dev.rollczi.litecommands.LiteCommandsBuilder;
import dev.rollczi.litecommands.argument.Arg;
import dev.rollczi.litecommands.argument.Args;
import dev.rollczi.litecommands.argument.block.Block;
import dev.rollczi.litecommands.argument.block.BlockArgument;
import dev.rollczi.litecommands.argument.enumeration.EnumArgument;
Expand All @@ -20,21 +21,28 @@
import dev.rollczi.litecommands.command.permission.LitePermissions;
import dev.rollczi.litecommands.command.permission.Permission;
import dev.rollczi.litecommands.command.section.Section;
import dev.rollczi.litecommands.handle.Redirector;
import dev.rollczi.litecommands.platform.LiteSender;
import dev.rollczi.litecommands.scheme.Scheme;
import dev.rollczi.litecommands.suggestion.Suggestion;
import panda.std.Blank;
import panda.std.Option;
import panda.std.Result;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.logging.Logger;

import static dev.rollczi.litecommands.suggestion.Suggestion.of;
import static panda.std.Blank.BLANK;

public final class LiteFactory {

private static final OneArgument<String> STRING_ARG = create(arg -> arg, "text");

private static final OneArgument<Boolean> BOOLEAN_ARG = OneArgument.create((invocation, argument) -> Option.of(argument)
.filter(arg -> arg.equalsIgnoreCase("true") || arg.equalsIgnoreCase("false"))
.map(Boolean::parseBoolean)
Expand All @@ -56,11 +64,10 @@ public final class LiteFactory {
private static final OneArgument<Double> DOUBLE_ARG = create(Double::parseDouble, "0", "1", "1.5", "10", "10.5", "100", "100.5");
private static final OneArgument<Float> FLOAT_ARG = create(Float::parseFloat, "0", "1", "1.5", "10", "10.5", "100", "100.5");

private LiteFactory() {
}
private static final Redirector<Scheme, String> MAP_SCHEME_TO_STRING = scheme -> String.join(System.lineSeparator(), scheme.getSchemes());
private static final Redirector<LitePermissions, String> MAP_PERMISSIONS_TO_STRING = scheme -> String.join(System.lineSeparator(), scheme.getPermissions());

private static <T> Result<T, Blank> parse(Supplier<T> parse) {
return Result.attempt(NumberFormatException.class, parse::get).mapErrToBlank();
private LiteFactory() {
}

public static <SENDER> LiteCommandsBuilder<SENDER> builder(Class<SENDER> senderType) {
Expand All @@ -82,7 +89,7 @@ public static <SENDER> LiteCommandsBuilder<SENDER> builder(Class<SENDER> senderT
.argument(Joiner.class, String.class, new JoinerArgument<>())
.argument(Block.class, Object.class, new BlockArgument<>())

.argument(String.class, (invocation, argument) -> Result.ok(argument))
.argument(String.class, STRING_ARG)
.argument(boolean.class, BOOLEAN_ARG)
.argument(Boolean.class, BOOLEAN_ARG)
.argument(long.class, LONG_ARG)
Expand All @@ -105,21 +112,44 @@ public static <SENDER> LiteCommandsBuilder<SENDER> builder(Class<SENDER> senderT
.resultHandler(boolean.class, (sender, invocation, value) -> {})
.resultHandler(Boolean.class, (sender, invocation, value) -> {})

.redirectResult(Scheme.class, String.class, scheme -> String.join(System.lineSeparator(), scheme.getSchemes()))
.redirectResult(LitePermissions.class, String.class, scheme -> String.join(System.lineSeparator(), scheme.getPermissions()))
.redirectResult(Scheme.class, String.class, MAP_SCHEME_TO_STRING)
.redirectResult(LitePermissions.class, String.class, MAP_PERMISSIONS_TO_STRING)

.contextualBind(LiteInvocation.class, (sender, invocation) -> Result.ok(invocation.toLite()))
.contextualBind(LiteSender.class, (sender, invocation) -> Result.ok(invocation.sender()))
.contextualBind(String[].class, (sender, invocation) -> Result.ok(invocation.arguments()))
.contextualBind(senderType, (sender, invocation) -> Result.ok(sender));
.contextualBind(senderType, (sender, invocation) -> Result.ok(sender))

.annotatedBind(String[].class, Args.class, (invocation, parameter, annotation) -> invocation.arguments())
.annotatedBind(List.class, Args.class, (invocation, parameter, annotation) -> Arrays.asList(invocation.arguments()))

.contextualBind(String[].class, (sender, invocation) -> {
Logger.getLogger("LiteCommands").warning("Add @Args to String[] parameter in command " + invocation.name());

return Result.ok(invocation.arguments());
})
;

}

private static <T> OneArgument<T> create(Function<String, T> parse, String... suggestions) {
return OneArgument.create((invocation, arg) -> parse(parse, arg), invocation -> Suggestion.of(suggestions), (inv, suggestion) -> validate(parse, suggestion));
return OneArgument.create(
(inv, arg) -> parse(parse, arg),
inv -> {
List<Suggestion> parsedSuggestions = new ArrayList<>(of(suggestions));
Optional<Suggestion> optionalSuggestion = inv.argument(inv.arguments().length - 1)
.filter(arg -> !arg.isEmpty())
.map(Suggestion::of);

optionalSuggestion.ifPresent(parsedSuggestions::add);

return parsedSuggestions;
},
(inv, suggestion) -> validate(parse, suggestion)
);
}

private static <T> Result<T, Blank> parse(Function<String, T> parse, String value) {
return Result.attempt(NumberFormatException.class, () -> parse.apply(value)).mapErrToBlank();
return Result.supplyThrowing(NumberFormatException.class, () -> parse.apply(value)).mapErrToBlank();
}

private static boolean validate(Function<String, ?> parse, Suggestion suggestion) {
Expand Down
Expand Up @@ -22,22 +22,22 @@ class InjectorContextProcessor<SENDER> {

Option<?> extract(Parameter parameter, Invocation<SENDER> invocation) {
Class<?> type = parameter.getType();
Map<Class<? extends Annotation>, Map<Class<?>, AnnotationBind<?, ?>>> annotationBinds = settings.getAnnotationBinds();
Map<Class<? extends Annotation>, Map<Class<?>, AnnotationBind<?, SENDER, ?>>> annotationBinds = settings.getAnnotationBinds();

for (Annotation annotation : parameter.getAnnotations()) {
Map<Class<?>, AnnotationBind<?, ?>> bindsByType = annotationBinds.get(annotation.annotationType());
Map<Class<?>, AnnotationBind<?, SENDER, ?>> bindsByType = annotationBinds.get(annotation.annotationType());

if (bindsByType == null) {
continue;
}

AnnotationBind<?, ?> annotationBind = bindsByType.get(parameter.getType());
AnnotationBind<?, SENDER, ?> annotationBind = bindsByType.get(parameter.getType());

if (annotationBind == null) {
continue;
}

return Option.of(handleExtract(annotationBind, parameter, annotation));
return Option.of(handleExtract(annotationBind, invocation, parameter, annotation));
}

Map<Class<?>, Contextual<SENDER, ?>> binds = this.settings.getContextualBinds();
Expand All @@ -49,8 +49,8 @@ Option<?> extract(Parameter parameter, Invocation<SENDER> invocation) {
}

@SuppressWarnings("unchecked")
private <S, A extends Annotation> Object handleExtract(AnnotationBind<S, A> annotationBind, Parameter parameter, Annotation annotation) {
return annotationBind.extract(parameter, (A) annotation);
private <TYPE, ANNOTATION extends Annotation> Object handleExtract(AnnotationBind<TYPE, SENDER, ANNOTATION> annotationBind, Invocation<SENDER> invocation, Parameter parameter, Annotation annotation) {
return annotationBind.extract(invocation, parameter, (ANNOTATION) annotation);
}

Option<Object> extract(Parameter parameter) {
Expand Down
Expand Up @@ -15,7 +15,7 @@ class LiteInjectorSettings<SENDER> implements InjectorSettings<SENDER> {

private final Map<Class<?>, TypeBind<?>> typeBinds = new HashMap<>();
private final Map<Class<?>, Contextual<SENDER, ?>> contextualBinds = new HashMap<>();
private final Map<Class<? extends Annotation>, Map<Class<?>, AnnotationBind<?, ?>>> annotationBinds = new HashMap<>();
private final Map<Class<? extends Annotation>, Map<Class<?>, AnnotationBind<?, SENDER, ?>>> annotationBinds = new HashMap<>();

@Override
public <T> LiteInjectorSettings<SENDER> typeBind(Class<T> type, Supplier<T> supplier) {
Expand All @@ -36,7 +36,7 @@ public <T> InjectorSettings<SENDER> typeBind(Class<T> type, TypeBind<T> typeBind
}

@Override
public <T, A extends Annotation> InjectorSettings<SENDER> annotationBind(Class<T> type, Class<A> on, AnnotationBind<T, A> annotationBind) {
public <T, A extends Annotation> InjectorSettings<SENDER> annotationBind(Class<T> type, Class<A> on, AnnotationBind<T, SENDER, A> annotationBind) {
this.annotationBinds.computeIfAbsent(on, k -> new HashMap<>()).put(type, annotationBind);
return this;
}
Expand Down Expand Up @@ -70,7 +70,7 @@ Map<Class<?>, TypeBind<?>> getTypeBinds() {
return typeBinds;
}

Map<Class<? extends Annotation>, Map<Class<?>, AnnotationBind<?, ?>>> getAnnotationBinds() {
Map<Class<? extends Annotation>, Map<Class<?>, AnnotationBind<?, SENDER, ?>>> getAnnotationBinds() {
return annotationBinds;
}

Expand Down
Expand Up @@ -15,7 +15,7 @@ public interface InjectorSettings<CONTEXT> {

<T> InjectorSettings<CONTEXT> typeBind(Class<T> type, TypeBind<T> typeBind);

<T, A extends Annotation> InjectorSettings<CONTEXT> annotationBind(Class<T> type, Class<A> on, AnnotationBind<T, A> annotationBind);
<T, A extends Annotation> InjectorSettings<CONTEXT> annotationBind(Class<T> type, Class<A> on, AnnotationBind<T, CONTEXT, A> annotationBind);

<T> InjectorSettings<CONTEXT> contextualBind(Class<T> on, Contextual<CONTEXT, T> contextual);

Expand Down
@@ -1,10 +1,12 @@
package dev.rollczi.litecommands.injector.bind;

import dev.rollczi.litecommands.command.Invocation;

import java.lang.annotation.Annotation;
import java.lang.reflect.Parameter;

public interface AnnotationBind<T, A extends Annotation> {
public interface AnnotationBind<TYPE, SENDER, ANNOTATION extends Annotation> {

T extract(Parameter parameter, A annotation);
TYPE extract(Invocation<SENDER> invocation, Parameter parameter, ANNOTATION annotation);

}
Expand Up @@ -11,24 +11,34 @@ public AssertResult(ExecuteResult executeResult) {
this.executeResult = executeResult;
}

public void assertSuccess() {
public AssertResult assertSuccess() {
Assertions.assertTrue(executeResult.isSuccess());
return this;
}

public void assertFail() {
public AssertResult assertFail() {
Assertions.assertTrue(executeResult.isFailure());
return this;
}

public void assertInvalid() {
public AssertResult assertInvalid() {
Assertions.assertTrue(executeResult.isInvalid());
return this;
}

public void assertResult(Object result) {
public AssertResult assertResult(Object result) {
Assertions.assertEquals(result, executeResult.getResult());
return this;
}

public void assertNullResult() {
public <T> T assertResultIs(Class<T> clazz) {
Assertions.assertTrue(clazz.isInstance(executeResult.getResult()));
return clazz.cast(executeResult.getResult());
}

public AssertResult assertNullResult() {
Assertions.assertNull(executeResult.getResult());
return this;
}

}
Expand Up @@ -26,6 +26,12 @@ public static TestPlatform withCommands(Class<?>... commands) {
return create(liteCommandsBuilder -> liteCommandsBuilder.command(commands));
}

public static TestPlatform withCommandsUniversalHandler(Class<?>... commands) {
return create(liteCommandsBuilder -> liteCommandsBuilder.command(commands)
.resultHandler(Object.class, (testHandle, invocation, value) -> {})
);
}

public interface Configurator {
void config(LiteCommandsBuilder<TestHandle> builder);
}
Expand Down
@@ -0,0 +1,53 @@
package dev.rollczi.litecommands.argument;

import dev.rollczi.litecommands.AssertResult;
import dev.rollczi.litecommands.TestFactory;
import dev.rollczi.litecommands.TestPlatform;
import dev.rollczi.litecommands.command.execute.Execute;
import dev.rollczi.litecommands.command.section.Section;
import org.junit.jupiter.api.Test;

import java.util.Arrays;
import java.util.List;

import static org.junit.jupiter.api.Assertions.assertArrayEquals;

class ArgsAnnotationTest {

TestPlatform platform = TestFactory.withCommandsUniversalHandler(Command.class, CommandList.class);

@Section(route = "test-primitive")
private static class Command {
@Execute
String[] test(@Args String[] arguments) {
return arguments;
}
}

@Section(route = "test-list")
private static class CommandList {
@Execute
List<String> test(@Args List<String> arguments) {
return arguments;
}
}

@Test
void testPrimitive() {
AssertResult success = platform.execute("test-primitive", "1", "2")
.assertSuccess();

String[] result = success
.assertResultIs(String[].class);

assertArrayEquals(new String[]{"1", "2"}, result);
}

@Test
void testList() {
platform.execute("test-list", "1", "2")
.assertSuccess()
.assertResult(Arrays.asList("1", "2"));
}

}
Expand Up @@ -26,7 +26,7 @@ String execute(@Annotation("custom") String test) {

TestPlatform platform = TestFactory.create(builder -> builder
.command(Command.class)
.annotatedBind(String.class, Annotation.class, (parameter, annotation) -> annotation.value())
.annotatedBind(String.class, Annotation.class, (invocation, parameter, annotation) -> annotation.value())
);

@Test
Expand Down
@@ -0,0 +1,26 @@
package dev.rollczi.litecommands.suggestion;

import dev.rollczi.litecommands.TestFactory;
import dev.rollczi.litecommands.TestPlatform;
import dev.rollczi.litecommands.argument.Arg;
import dev.rollczi.litecommands.command.execute.Execute;
import dev.rollczi.litecommands.command.section.Section;
import org.junit.jupiter.api.Test;

class SuggestionStringTest {

TestPlatform platform = TestFactory.withCommands(Command.class);

@Section(route = "test")
private static class Command {
@Execute void test(@Arg String arg) {}
}

@Test
void test() {
platform.suggest("test", "").assertWith("text");
platform.suggest("test", "t").assertWith("t", "text");
platform.suggest("test", "siema").assertWith("siema");
}

}

0 comments on commit 7aa3fe8

Please sign in to comment.