Skip to content

Commit

Permalink
patch: added parameter preconditions (#80)
Browse files Browse the repository at this point in the history
* patch: added parameter preconditions

* patch: code cleanup

* patch: fixed renames

* patch: fixed type

* patch: fixed benchmarks

* patch: deleted utils class
  • Loading branch information
k-boyle authored Jun 13, 2021
1 parent 1ef2a44 commit 6451e33
Show file tree
Hide file tree
Showing 26 changed files with 318 additions and 170 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package kboyle.oktane.core.module;

import kboyle.oktane.core.results.command.CommandNOPResult;

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

Expand All @@ -23,9 +25,16 @@ public BenchmarkCommandBuilder withRemainderParameter() {
}

public Command create() {
var builder = Command.builder();
var dummyModule = CommandModule.builder()
.withName(String.valueOf(counter++))
.withGroup("")
.build();

var builder = Command.builder()
.withName(String.valueOf(counter++))
.withCallback((ctx, beans, parameters) -> new CommandNOPResult(ctx.command()).mono());
parameters.forEach(builder::withParameter);

return new Command(null, builder);
return new Command(dummyModule, builder);
}
}
37 changes: 0 additions & 37 deletions OktaneCore/src/main/java/kboyle/oktane/core/CollectionUtils.java

This file was deleted.

20 changes: 18 additions & 2 deletions OktaneCore/src/main/java/kboyle/oktane/core/CommandContext.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package kboyle.oktane.core;

import kboyle.oktane.core.module.Command;
import kboyle.oktane.core.module.CommandParameter;
import kboyle.oktane.core.prefix.Prefix;

import java.util.Optional;
Expand All @@ -12,10 +13,11 @@ public class CommandContext {
private final BeanProvider beanProvider;

Command command;

String input;

Prefix prefix;
Object[] parsedArguments;
Object currentArgument;
CommandParameter currentParameter;

public CommandContext(BeanProvider beanProvider) {
this.beanProvider = beanProvider;
Expand Down Expand Up @@ -52,4 +54,18 @@ public String input() {
public Optional<Prefix> prefix() {
return Optional.ofNullable(prefix);
}

/**
* @return The current argument having preconditions run against it.
*/
public Object currentArgument() {
return currentArgument;
}

/**
* @return The current parameter having preconditions run against it.
*/
public CommandParameter currentParameter() {
return currentParameter;
}
}
17 changes: 12 additions & 5 deletions OktaneCore/src/main/java/kboyle/oktane/core/CommandHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
import kboyle.oktane.core.parsers.TypeParser;
import kboyle.oktane.core.prefix.DefaultPrefixHandler;
import kboyle.oktane.core.results.Result;
import kboyle.oktane.core.results.argumentparser.ArgumentParserResult;
import kboyle.oktane.core.results.search.CommandMatchFailedResult;
import kboyle.oktane.core.results.search.CommandNotFoundResult;
import kboyle.oktane.core.results.search.MissingPrefixResult;
Expand Down Expand Up @@ -178,18 +177,26 @@ private Mono<Result> executeUntilSuccess(
return executeUntilSuccess(context, matchesIterator, resultAggregator);
}

return executeCommand(context, argumentParserResult);
context.parsedArguments = argumentParserResult.parsedArguments();
return CommandUtils.runParameterPreconditions(context, context.command)
.flatMap(parameterPreconditionResult -> {
if (!parameterPreconditionResult.success()) {
resultAggregator.add(parameterPreconditionResult);
return executeUntilSuccess(context, matchesIterator, resultAggregator);
}

return executeCommand(context);
});
});
});
}

private Mono<Result> executeCommand(CONTEXT context, ArgumentParserResult parserResult) {
private Mono<Result> executeCommand(CONTEXT context) {
var command = context.command();
System.out.println("executing " + command);
context.command = command;
var beans = getBeans(context, command.module.beans);
return command.commandCallback
.execute(context, beans, parserResult.parsedArguments())
.execute(context, beans, context.parsedArguments)
.cast(Result.class);
}

Expand Down
105 changes: 93 additions & 12 deletions OktaneCore/src/main/java/kboyle/oktane/core/CommandUtils.java
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
package kboyle.oktane.core;

import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import kboyle.oktane.core.module.Command;
import kboyle.oktane.core.module.CommandModule;
import kboyle.oktane.core.module.CommandParameter;
import kboyle.oktane.core.module.Precondition;
import kboyle.oktane.core.results.precondition.ParameterPreconditionsFailedResult;
import kboyle.oktane.core.results.precondition.PreconditionResult;
import kboyle.oktane.core.results.precondition.PreconditionSuccessfulResult;
import kboyle.oktane.core.results.precondition.PreconditionsFailedResult;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.function.Consumer;
import java.util.stream.Stream;

Expand All @@ -22,7 +27,7 @@ public enum CommandUtils {
private static final Mono<PreconditionResult> SUCCESS = Mono.just(PreconditionSuccessfulResult.get());

/**
* Runs the given preconditions.
* Runs the given {@link Precondition}s.
*
* @param context The context to pass to {@link Precondition#run(CommandContext, Command)}.
* @param command The command to pass to {@link Precondition#run(CommandContext, Command)}.
Expand All @@ -34,19 +39,95 @@ public static Mono<PreconditionResult> runPreconditions(CommandContext context,
return SUCCESS;
}

return Flux.fromIterable(preconditions)
.flatMap(precondition -> precondition.run(context, command))
.collectList()
.map(results -> {
var failedResults = results.stream()
.filter(result -> !result.success())
.collect(ImmutableList.toImmutableList());
return runPreconditions(context, command, preconditions.iterator(), new ArrayList<>());
}

/**
* Runs the {@link Precondition}s for the parameters of the given {@link Command}.
*
* @param context The context to pass to {@link Precondition#run(CommandContext, Command)}.
* @param command The command to pass to {@link Precondition#run(CommandContext, Command)}.
* @return The result of running all of the preconditions.
*/
public static Mono<PreconditionResult> runParameterPreconditions(CommandContext context, Command command) {
Preconditions.checkState(
command.parameters.size() == context.parsedArguments.length,
"Mismatch between parameters and arguments"
);

if (command.parameters.isEmpty()) {
return SUCCESS;
}

return runParameterPreconditions(context, command, 0, command.parameters, new ArrayList<>());
}

private static Mono<PreconditionResult> runParameterPreconditions(
CommandContext context,
Command command,
int index,
ImmutableList<CommandParameter> parameters,
List<PreconditionResult> aggregatorList) {
if (index == parameters.size()) {
context.currentArgument = null;
context.currentParameter = null;

if (aggregatorList.isEmpty()) {
return SUCCESS;
}

if (aggregatorList.size() == 1) {
return aggregatorList.get(0).mono();
}

return new PreconditionsFailedResult(aggregatorList).mono();
}

var currentParameter = parameters.get(index);
var preconditions = currentParameter.preconditions;
if (preconditions.isEmpty()) {
return runParameterPreconditions(context, command, index + 1, parameters, aggregatorList);
}

var currentArgument = context.parsedArguments[index];
context.currentParameter = currentParameter;
context.currentArgument = currentArgument;

return runPreconditions(context, command, preconditions.iterator(), new ArrayList<>())
.flatMap(result -> {
if (!result.success()) {
aggregatorList.add(new ParameterPreconditionsFailedResult(currentParameter, currentArgument, result));
}

return runParameterPreconditions(context, command, index + 1, parameters, aggregatorList);
});
}

private static Mono<PreconditionResult> runPreconditions(
CommandContext context,
Command command,
Iterator<Precondition> preconditionIterator,
List<PreconditionResult> aggregatorList) {
if (!preconditionIterator.hasNext()) {
if (aggregatorList.isEmpty()) {
return SUCCESS;
}

if (aggregatorList.size() == 1) {
return aggregatorList.get(0).mono();
}

return new PreconditionsFailedResult(aggregatorList).mono();
}

if (failedResults.isEmpty()) {
return PreconditionSuccessfulResult.get();
return preconditionIterator.next()
.run(context, command)
.flatMap(result -> {
if (!result.success()) {
aggregatorList.add(result);
}

return new PreconditionsFailedResult(failedResults);
return runPreconditions(context, command, preconditionIterator, aggregatorList);
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@
import reactor.core.publisher.Mono;

import java.lang.reflect.Method;
import java.util.*;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.function.Consumer;
import java.util.stream.Collectors;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,12 @@
import kboyle.oktane.core.results.precondition.PreconditionResult;
import reactor.core.publisher.Mono;

import java.util.*;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.ToIntFunction;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@
import com.google.common.base.MoreObjects;
import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import kboyle.oktane.core.parsers.TypeParser;

import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

/**
Expand All @@ -22,6 +25,7 @@ public final class CommandParameter {
public final boolean optional;
public final Optional<String> defaultValue;
public final Optional<Parameter> originalParameter;
public final ImmutableList<Precondition> preconditions;

CommandParameter(Command command, Builder builder) {
Preconditions.checkState(!Strings.isNullOrEmpty(builder.name), "A parameter name must be a non-empty value");
Expand All @@ -36,6 +40,7 @@ public final class CommandParameter {
this.optional = builder.optional;
this.defaultValue = Optional.ofNullable(builder.defaultValue);
this.originalParameter = Optional.ofNullable(builder.originalParameter);
this.preconditions = ImmutableList.copyOf(builder.preconditions);
}

public static Builder builder() {
Expand All @@ -50,6 +55,8 @@ public String toString() {
}

public static class Builder {
public final List<Precondition> preconditions;

private Class<?> type;
private String description;
private String name;
Expand All @@ -60,6 +67,7 @@ public static class Builder {
private Parameter originalParameter;

private Builder() {
this.preconditions = new ArrayList<>();
}

public Builder withType(Class<?> type) {
Expand Down Expand Up @@ -110,6 +118,11 @@ public Builder withOriginalParameter(Parameter originalParameter) {
return this;
}

public Builder withPrecondition(Precondition precondition) {
this.preconditions.add(precondition);
return this;
}

CommandParameter build(Command command) {
return new CommandParameter(command, this);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ public Command.Builder createCommand(Aliases moduleGroups, Method method) {

commandBuilder.withCallback(getCallback(method, synchronised));

var parameterFactory = new CommandParameterFactory(typeParserByClass);
var parameterFactory = new CommandParameterFactory(typeParserByClass, preconditionFactoryMap);
var parameters = method.getParameters();
for (var parameter : parameters) {
var commandParameter = parameterFactory.createParameter(parameter);
Expand Down
Loading

0 comments on commit 6451e33

Please sign in to comment.