With.java
package com.github.dakusui.actionunit.actions;
import com.github.dakusui.actionunit.core.Action;
import com.github.dakusui.actionunit.core.Context;
import java.util.Formatter;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Function;
import static com.github.dakusui.actionunit.core.ActionSupport.simple;
import static com.github.dakusui.actionunit.core.context.FormattableConsumer.nopConsumer;
import static com.github.dakusui.actionunit.utils.InternalUtils.toStringIfOverriddenOrNoname;
import static com.github.dakusui.printables.PrintableFunctionals.*;
import static java.util.Objects.requireNonNull;
/**
* An interface to access a context variable safely.
* An instance of this interface corresponds to single context variable.
*
* @param <V> A type of variable through which this action interacts with another.
* @see Context
*/
public interface With<V> extends Contextful<V> {
/**
* Returns a "close" action which takes care of "clean up"
*
* @return A "close" action.
*/
Optional<Action> close();
@Override
default void accept(Visitor visitor) {
visitor.visit(this);
}
@Override
default void formatTo(Formatter formatter, int flags, int width, int precision) {
formatter.format("with:" + variableName() + ":" + toStringIfOverriddenOrNoname(valueSource()));
}
class Builder<V> extends Contextful.Builder<Builder<V>, With<V>, V, V> {
public Builder(String variableName, Function<Context, V> function) {
super(variableName, function);
}
/**
* Creates an action that updates the context variable.
* A current value of the context variable is given to the `function` and the result
* of the function is written back to the context.
*
* @param function A function to compute a new value of the context variable.
* @return An action that updates the context variable.
*/
public Action updateContextVariableWith(Function<V, V> function) {
return simple(
"update:" + variableName() + "*",
printableConsumer((Context c) -> variableUpdateFunction(function).apply(c)).describe(toStringIfOverriddenOrNoname(function)));
}
public With<V> build() {
return build(nopConsumer());
}
public With<V> build(Consumer<V> finisher) {
return new Impl<>(this.variableName(), this.internalVariableName(), this.valueSource(), this.action(), finisher);
}
public <W> Builder<W> nest(Function<V, W> function) {
return new Builder<>(nextVariableName(variableName()), function(function));
}
protected static String nextVariableName(String variableName) {
requireNonNull(variableName);
if (variableName.length() == 1 && 'a' <= variableName.charAt(0) && variableName.charAt(0) <= 'z')
return Character.toString((char) (variableName.charAt(0) + 1));
if (variableName.matches(".*_[1-9][0-9]*$")) {
int index = Integer.parseInt(variableName.replaceAll(".*_", "")) + 1;
return variableName.replaceAll("_[1-9][0-9]*$", "_" + index);
}
return variableName + "_1";
}
private Function<Context, V> variableUpdateFunction(Function<V, V> function) {
return printableFunction(
(Context context) -> {
V ret = function.apply(resolveValue(context));
context.assignTo(internalVariableName(), ret);
return ret;
})
.describe(toStringIfOverriddenOrNoname(function));
}
private static class Impl<V> extends Base<V, V> implements With<V> {
private final Action end;
public Impl(String variableName, String internalVariableName, Function<Context, V> valueSource, Action action, Consumer<V> finisher) {
super(variableName, internalVariableName, valueSource, action);
this.end = simple(String.format("done:%s", finisher),
printableConsumer((Context context) -> {
V variable = context.valueOf(internalVariableName);
context.unassign(internalVariableName); // Un-assign first. Otherwise, finisher may fail.
if (finisher != null)
finisher.accept(variable);
}).describe(String.format("cleanUp:%s", variableName)));
}
@Override
public Optional<Action> close() {
return Optional.ofNullable(end);
}
}
}
}