diff --git a/extensions/rimor-params/src/main/java/st/networkers/rimor/params/InstructionParam.java b/extensions/rimor-params/src/main/java/st/networkers/rimor/params/InstructionParam.java index 152ee59..b43ca54 100644 --- a/extensions/rimor-params/src/main/java/st/networkers/rimor/params/InstructionParam.java +++ b/extensions/rimor-params/src/main/java/st/networkers/rimor/params/InstructionParam.java @@ -9,15 +9,16 @@ import java.lang.annotation.Target; /** - * Method parameters with this annotation are injected with the corresponding command parameter. + * Instruction handler method parameters with this annotation are injected with the corresponding command parameter. * - *

If the index of the command parameter is not manually specified in this annotation, it is automatically - * detected by the position of this parameter relative to all the method's {@code @Param}-annotated parameters. + *

If the index of the parameter is not manually specified in this annotation, it is automatically + * detected by the position of this parameter relative to all the method's {@code @InstructionParam}-annotated parameters. * - *

The injected object will be {@code null} if the index is greater than the command parameter size. + *

The injected object will be {@code null} if the index is greater or equal than the number of command parameters. * *

If the type of the method parameter matches the type of the command parameter, it is directly injected. Also, - * the object can be parsed and injected into, for example, a Boolean or Enum parameter, following the registered {@link InstructionParamParser}s. + * the object can be parsed and injected into, for example, a Boolean or Enum method parameter from a String command + * parameter, following the registered {@link InstructionParamParser instruction param parsers}. */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.PARAMETER) diff --git a/extensions/rimor-params/src/main/java/st/networkers/rimor/params/ParamsExtension.java b/extensions/rimor-params/src/main/java/st/networkers/rimor/params/ParamsExtension.java index da4c86c..485b3e3 100644 --- a/extensions/rimor-params/src/main/java/st/networkers/rimor/params/ParamsExtension.java +++ b/extensions/rimor-params/src/main/java/st/networkers/rimor/params/ParamsExtension.java @@ -2,10 +2,7 @@ import st.networkers.rimor.Rimor; import st.networkers.rimor.extension.RimorExtension; -import st.networkers.rimor.params.parse.support.BooleanInstructionParamParser; -import st.networkers.rimor.params.parse.support.DefaultInstructionParamParser; -import st.networkers.rimor.params.parse.support.EnumInstructionParamParser; -import st.networkers.rimor.params.parse.support.StringInstructionParamParser; +import st.networkers.rimor.params.parse.support.*; public class ParamsExtension implements RimorExtension { @@ -14,6 +11,7 @@ public void configure(Rimor rimor) { rimor.registerExecutionContextProvider(new BooleanInstructionParamParser()) .registerExecutionContextProvider(new DefaultInstructionParamParser()) .registerExecutionContextProvider(new EnumInstructionParamParser()) - .registerExecutionContextProvider(new StringInstructionParamParser()); + .registerExecutionContextProvider(new StringInstructionParamParser()) + .registerExecutionContextProvider(new TextInstructionParamParser()); } } diff --git a/extensions/rimor-params/src/main/java/st/networkers/rimor/params/parse/AbstractInstructionParamParser.java b/extensions/rimor-params/src/main/java/st/networkers/rimor/params/parse/AbstractInstructionParamParser.java index 815943b..8e5e1db 100644 --- a/extensions/rimor-params/src/main/java/st/networkers/rimor/params/parse/AbstractInstructionParamParser.java +++ b/extensions/rimor-params/src/main/java/st/networkers/rimor/params/parse/AbstractInstructionParamParser.java @@ -1,12 +1,12 @@ package st.networkers.rimor.params.parse; import st.networkers.rimor.context.ExecutionContext; -import st.networkers.rimor.context.Token; import st.networkers.rimor.context.ParameterToken; +import st.networkers.rimor.context.Token; +import st.networkers.rimor.context.provide.AbstractExecutionContextProvider; import st.networkers.rimor.params.InstructionParam; import st.networkers.rimor.params.InstructionParams; import st.networkers.rimor.params.parse.support.StringInstructionParamParser; -import st.networkers.rimor.context.provide.AbstractExecutionContextProvider; import st.networkers.rimor.reflect.CachedMethod; import st.networkers.rimor.reflect.CachedParameter; @@ -15,19 +15,13 @@ import java.util.List; /** - * Abstract useful class for implementing {@link InstructionParamParser}s. + * Abstract useful class for providers that just parse an element from the {@link InstructionParams}-annotated lists. *

* Check {@link StringInstructionParamParser} for a quick example. */ public abstract class AbstractInstructionParamParser extends AbstractExecutionContextProvider implements InstructionParamParser { - protected static final Token> PARAMS_TOKEN = new Token>() {}.annotatedWith(InstructionParams.class); - - @SafeVarargs - protected AbstractInstructionParamParser(Class providedType, Class... otherTypes) { - super(providedType, otherTypes); - this.annotatedWith(InstructionParam.class); - } + public static final Token> PARAMS_TOKEN = new Token>() {}.annotatedWith(InstructionParams.class); protected AbstractInstructionParamParser(Type providedType, Type... otherTypes) { super(providedType, otherTypes); @@ -39,32 +33,33 @@ public T get(Token token, ExecutionContext context) { return this.parse(this.getParameter(token, context), token, context); } - private Object getParameter(Token token, ExecutionContext context) { - int position = this.getPosition(token, context); + protected Object getParameter(Token token, ExecutionContext context) { + int index = getIndex(token, context); + if (index < 0) + return null; + List commandParameters = context.get(PARAMS_TOKEN) .orElseThrow(() -> new IllegalArgumentException("there is no @InstructionParams annotated List in the execution context!")); - return position >= 0 - ? position < commandParameters.size() ? commandParameters.get(position) : null - : null; + return index < commandParameters.size() ? commandParameters.get(index) : null; } - private int getPosition(Token token, ExecutionContext context) { + protected static int getIndex(Token token, ExecutionContext context) { InstructionParam param = token.getAnnotation(InstructionParam.class); - // if position is indicated, just return it + // if index is given, just return it if (param.index() > -1) return param.index(); if (token instanceof ParameterToken) { - ParameterToken parameterToken = (ParameterToken) token; - return this.getPositionFromParameter(parameterToken.getMethod(), parameterToken.getParameter()); + ParameterToken parameterToken = (ParameterToken) token; + return getPositionFromParameter(parameterToken.getMethod(), parameterToken.getParameter()); } return -1; } - private int getPositionFromParameter(CachedMethod method, CachedParameter parameter) { + protected static int getPositionFromParameter(CachedMethod method, CachedParameter parameter) { // TODO cache List parameters = new ArrayList<>(method.getParameters()); parameters.removeIf(p -> !p.isAnnotationPresent(InstructionParam.class) diff --git a/extensions/rimor-params/src/main/java/st/networkers/rimor/params/parse/support/Text.java b/extensions/rimor-params/src/main/java/st/networkers/rimor/params/parse/support/Text.java new file mode 100644 index 0000000..2776220 --- /dev/null +++ b/extensions/rimor-params/src/main/java/st/networkers/rimor/params/parse/support/Text.java @@ -0,0 +1,23 @@ +package st.networkers.rimor.params.parse.support; + +import st.networkers.rimor.annotation.RimorQualifier; +import st.networkers.rimor.params.InstructionParam; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Indicates that a {@code String} {@link InstructionParam parameter} is a text parameter, i.e., the following command + * parameters will be concatenated into a single String and this parameter won't only contain the first word of the text. + * + *

The {@link InstructionParam#index() parameter index} indicates the first parameter that counts as text. If the + * number of parameters in an execution is less than the index, the whole text parameter will be {@code null} (instead + * of an empty string). + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.PARAMETER) +@RimorQualifier +public @interface Text { +} diff --git a/extensions/rimor-params/src/main/java/st/networkers/rimor/params/parse/support/TextInstructionParamParser.java b/extensions/rimor-params/src/main/java/st/networkers/rimor/params/parse/support/TextInstructionParamParser.java new file mode 100644 index 0000000..1e7723b --- /dev/null +++ b/extensions/rimor-params/src/main/java/st/networkers/rimor/params/parse/support/TextInstructionParamParser.java @@ -0,0 +1,38 @@ +package st.networkers.rimor.params.parse.support; + +import org.apache.commons.lang3.StringUtils; +import st.networkers.rimor.annotation.RequireQualifiers; +import st.networkers.rimor.context.ExecutionContext; +import st.networkers.rimor.context.Token; +import st.networkers.rimor.params.InstructionParam; +import st.networkers.rimor.params.parse.AbstractInstructionParamParser; + +import java.util.List; + +@RequireQualifiers({InstructionParam.class, Text.class}) +public class TextInstructionParamParser extends AbstractInstructionParamParser { + + public TextInstructionParamParser() { + super(String.class); + } + + @Override + public String get(Token token, ExecutionContext context) { + int index = getIndex(token, context); + if (index < 0) + return null; + + List commandParameters = context.get(PARAMS_TOKEN) + .orElseThrow(() -> new IllegalArgumentException("there is no @InstructionParams annotated List in the execution context!")); + + return index < commandParameters.size() + ? StringUtils.join(commandParameters.subList(index, commandParameters.size()), " ") + : null; + } + + @Override + public String parse(Object parameter, Token token, ExecutionContext context) { + // unused, overriding #get(Token, ExecutionContext) + return null; + } +} diff --git a/extensions/rimor-params/src/test/java/st/networkers/rimor/params/parse/support/TextInstructionParamParserTest.java b/extensions/rimor-params/src/test/java/st/networkers/rimor/params/parse/support/TextInstructionParamParserTest.java new file mode 100644 index 0000000..4e9425e --- /dev/null +++ b/extensions/rimor-params/src/test/java/st/networkers/rimor/params/parse/support/TextInstructionParamParserTest.java @@ -0,0 +1,71 @@ +package st.networkers.rimor.params.parse.support; + +import org.junit.jupiter.api.Test; +import st.networkers.rimor.context.ExecutionContext; +import st.networkers.rimor.context.Token; +import st.networkers.rimor.params.InstructionParamImpl; +import st.networkers.rimor.params.parse.AbstractInstructionParamParser; + +import java.util.Arrays; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class TextInstructionParamParserTest { + + @Test + void givenTextParameterOfIndex0_whenGettingText_containsAllParams() { + Token token = Token.of(String.class) + .annotatedWith(new InstructionParamImpl(null, null, 0)) + .annotatedWith(Text.class); + + ExecutionContext context = ExecutionContext.builder() + .bind(AbstractInstructionParamParser.PARAMS_TOKEN, Arrays.asList("foo", "bar", "baz", "qux", "quux")) + .build(); + + assertThat(new TextInstructionParamParser().get(token, context)).isEqualTo("foo bar baz qux quux"); + } + + @Test + void givenTextParameterOfIndex2_whenGettingText_containsAllButFirstTwoParams() { + Token token = Token.of(String.class) + .annotatedWith(new InstructionParamImpl(null, null, 2)) + .annotatedWith(Text.class); + + ExecutionContext context = ExecutionContext.builder() + .bind(AbstractInstructionParamParser.PARAMS_TOKEN, Arrays.asList("foo", "bar", "baz", "qux", "quux")) + .build(); + + assertThat(new TextInstructionParamParser().get(token, context)).isEqualTo("baz qux quux"); + } + + @Test + void givenTextParameterOfIndexOutOfBounds_whenGettingText_returnsNull() { + Token token = Token.of(String.class) + .annotatedWith(new InstructionParamImpl(null, null, 2)) + .annotatedWith(Text.class); + + ExecutionContext context = ExecutionContext.builder() + .bind(AbstractInstructionParamParser.PARAMS_TOKEN, Arrays.asList("foo", "bar")) + .build(); + + assertThat(new TextInstructionParamParser().get(token, context)).isNull(); + } + + @Test + void givenTextParameter_whenGettingText_stringRepresentationOfEachParamIsUsed() { + Object mockedObject = mock(Object.class); + when(mockedObject.toString()).thenReturn("baz"); + + Token token = Token.of(String.class) + .annotatedWith(new InstructionParamImpl(null, null, 0)) + .annotatedWith(Text.class); + + ExecutionContext context = ExecutionContext.builder() + .bind(AbstractInstructionParamParser.PARAMS_TOKEN, Arrays.asList("foo", "bar", mockedObject)) + .build(); + + assertThat(new TextInstructionParamParser().get(token, context)).isEqualTo("foo bar baz"); + } +}