Skip to content

Commit

Permalink
feat: create text instruction parameters
Browse files Browse the repository at this point in the history
  • Loading branch information
mimbrero committed Sep 6, 2023
1 parent 59e09f6 commit 6e8bb85
Show file tree
Hide file tree
Showing 6 changed files with 156 additions and 30 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
* <p>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.
* <p>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.
*
* <p>The injected object will be {@code null} if the index is greater than the command parameter size.
* <p>The injected object will be {@code null} if the index is greater or equal than the number of command parameters.
*
* <p>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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand All @@ -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());
}
}
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -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.
* <p>
* Check {@link StringInstructionParamParser} for a quick example.
*/
public abstract class AbstractInstructionParamParser<T> extends AbstractExecutionContextProvider<T> implements InstructionParamParser<T> {

protected static final Token<List<Object>> PARAMS_TOKEN = new Token<List<Object>>() {}.annotatedWith(InstructionParams.class);

@SafeVarargs
protected AbstractInstructionParamParser(Class<? extends T> providedType, Class<? extends T>... otherTypes) {
super(providedType, otherTypes);
this.annotatedWith(InstructionParam.class);
}
public static final Token<List<Object>> PARAMS_TOKEN = new Token<List<Object>>() {}.annotatedWith(InstructionParams.class);

protected AbstractInstructionParamParser(Type providedType, Type... otherTypes) {
super(providedType, otherTypes);
Expand All @@ -39,32 +33,33 @@ public T get(Token<T> token, ExecutionContext context) {
return this.parse(this.getParameter(token, context), token, context);
}

private Object getParameter(Token<T> token, ExecutionContext context) {
int position = this.getPosition(token, context);
protected Object getParameter(Token<T> token, ExecutionContext context) {
int index = getIndex(token, context);
if (index < 0)
return null;

List<Object> commandParameters = context.get(PARAMS_TOKEN)
.orElseThrow(() -> new IllegalArgumentException("there is no @InstructionParams annotated List<String> 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<T> 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<T> parameterToken = (ParameterToken<T>) 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<CachedParameter> parameters = new ArrayList<>(method.getParameters());
parameters.removeIf(p -> !p.isAnnotationPresent(InstructionParam.class)
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
*
* <p>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 {
}
Original file line number Diff line number Diff line change
@@ -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<String> {

public TextInstructionParamParser() {
super(String.class);
}

@Override
public String get(Token<String> token, ExecutionContext context) {
int index = getIndex(token, context);
if (index < 0)
return null;

List<Object> commandParameters = context.get(PARAMS_TOKEN)
.orElseThrow(() -> new IllegalArgumentException("there is no @InstructionParams annotated List<String> in the execution context!"));

return index < commandParameters.size()
? StringUtils.join(commandParameters.subList(index, commandParameters.size()), " ")
: null;
}

@Override
public String parse(Object parameter, Token<String> token, ExecutionContext context) {
// unused, overriding #get(Token, ExecutionContext)
return null;
}
}
Original file line number Diff line number Diff line change
@@ -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<String> 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<String> 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<String> 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<String> 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");
}
}

0 comments on commit 6e8bb85

Please sign in to comment.