All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.rouz.task.TaskBuilders Maven / Gradle / Ivy

package io.rouz.task;

import java.io.Serializable;
import java.util.Collections;
import java.util.List;
import java.util.stream.Stream;

import io.rouz.task.TaskContext.Value;
import io.rouz.task.dsl.TaskBuilder;
import io.rouz.task.dsl.TaskBuilder.F0;
import io.rouz.task.dsl.TaskBuilder.F1;
import io.rouz.task.dsl.TaskBuilder.F2;
import io.rouz.task.dsl.TaskBuilder.F3;
import io.rouz.task.dsl.TaskBuilder.F4;
import io.rouz.task.dsl.TaskBuilder.TaskBuilder1;
import io.rouz.task.dsl.TaskBuilder.TaskBuilder2;
import io.rouz.task.dsl.TaskBuilder.TaskBuilder3;
import io.rouz.task.dsl.TaskBuilder.TaskBuilderC;
import io.rouz.task.dsl.TaskBuilder.TaskBuilderC0;
import io.rouz.task.dsl.TaskBuilder.TaskBuilderCV;
import io.rouz.task.dsl.TaskBuilder.TaskBuilderCV0;

import static java.util.stream.Collectors.toList;

/**
 * Package local implementation of the {@link TaskBuilder} tree.
 *
 * These classes tackle the exponential growth of paths that can be taken through the
 * {@link TaskBuilder}X interfaces by linearizing the implementation through composing functions.
 *
 * The linearization is implemented by letting the next builder in the chain take either a
 * {@link RecursiveEval} or {@link ChainingEval}. This evaluator allows the builder to chain
 * onto the evaluation by including more input tasks. The evaluator will finally be used to
 * terminate the builder by enclosing a function into an {@link EvalClosure} for a {@link Task}.
 */
final class TaskBuilders {

  static  TaskBuilder rootBuilder(TaskId taskId, Class type) {
    return new Builder0<>(taskId, type);
  }

  // #############################################################################################

  private static class Builder0 extends BaseRefs implements TaskBuilder {

    Builder0(TaskId taskId, Class type) {
      super(taskId, type);
    }

    @Override
    public Task process(F0 code) {
      return Task.create(inputs, type, gated(taskId, code), taskId);
    }

    @Override
    public Task processWithContext(F1> code) {
      return Task.create(inputs, type, gatedVal(taskId, code), taskId);
    }

    @Override
    public  TaskBuilder1 in(F0> aTask) {
      F0> aTaskSingleton = Singleton.create(aTask);
      TaskId taskId = this.taskId; // local ref to drop ref to Builder0 instance
      return new Builder1<>(
          lazyFlatten(inputs, lazyList(aTaskSingleton)),
          taskId, type,
          leafEvalFn(tc -> {
            Value aValue = tc.evaluate(aTaskSingleton.get());
            return f1 -> aValue.flatMap(gated(taskId, tc, f1));
          }),
          leafEvalFn(tc -> {
            Value aValue = tc.evaluate(aTaskSingleton.get());
            return f1 -> aValue.flatMap(gatedVal(taskId, tc, f1));
          }));
    }

    @Override
    public  TaskBuilder1, Z> ins(F0>> aTasks) {
      F0>> aTasksSingleton = Singleton.create(aTasks);
      TaskId taskId = this.taskId; // local ref to drop ref to Builder0 instance
      return new Builder1<>(
          lazyFlatten(inputs, lazyFlatten(aTasksSingleton)),
          taskId, type,
          leafEvalFn(tc -> {
            Value> aListValue = aTasksSingleton.get()
                .stream().map(tc::evaluate).collect(tc.toValueList());
            return f1 -> aListValue.flatMap(gated(taskId, tc, f1));
          }),
          leafEvalFn(tc -> {
            Value> aListValue = aTasksSingleton.get()
                .stream().map(tc::evaluate).collect(tc.toValueList());
            return f1 -> aListValue.flatMap(gatedVal(taskId, tc, f1));
          }));
    }

    @Override
    public TaskBuilderC0 curried() {
      return new BuilderC0<>(taskId, type);
    }

    @Override
    public TaskBuilderCV0 curriedWithContext() {
      return new BuilderCV0<>(taskId, type);
    }
  }

  private static class BuilderC0 extends BaseRefs implements TaskBuilderC0 {

    BuilderC0(TaskId taskId, Class type) {
      super(taskId, type);
    }

    @Override
    public  TaskBuilderC in(F0> aTask) {
      F0> aTaskSingleton = Singleton.create(aTask);
      return new BuilderC<>(
          lazyFlatten(inputs, lazyList(aTaskSingleton)),
          taskId, type,
          leafEval(
              taskId,
              tc -> tc.evaluate(aTaskSingleton.get())));
    }

    @Override
    public  TaskBuilderC, Z, Z> ins(F0>> aTasks) {
      F0>> aTasksSingleton = Singleton.create(aTasks);
      return new BuilderC<>(
          lazyFlatten(inputs, lazyFlatten(aTasksSingleton)),
          taskId, type,
          leafEval(
              taskId,
              tc -> aTasksSingleton.get()
                  .stream().map(tc::evaluate).collect(tc.toValueList())));
    }
  }

  private static class BuilderCV0 extends BaseRefs implements TaskBuilderCV0 {

    BuilderCV0(TaskId taskId, Class type) {
      super(taskId, type);
    }

    @Override
    public  TaskBuilderCV, Z> in(F0> aTask) {
      F0> aTaskSingleton = Singleton.create(aTask);
      return new BuilderCV<>(
          lazyFlatten(inputs, lazyList(aTaskSingleton)),
          taskId, type,
          leafValEval(
              taskId,
              tc -> tc.evaluate(aTaskSingleton.get())));
    }

    @Override
    public  TaskBuilderCV, Value, Z> ins(F0>> aTasks) {
      F0>> aTasksSingleton = Singleton.create(aTasks);
      return new BuilderCV<>(
          lazyFlatten(inputs, lazyFlatten(aTasksSingleton)),
          taskId, type,
          leafValEval(
              taskId,
              tc -> aTasksSingleton.get()
                  .stream().map(tc::evaluate).collect(tc.toValueList())));
    }
  }

  // #############################################################################################

  private static class BuilderC extends BaseRefs implements TaskBuilderC {

    private final RecursiveEval evaluator;

    private BuilderC(
        F0>> inputs, TaskId taskId, Class type, RecursiveEval evaluator) {
      super(inputs, taskId, type);
      this.evaluator = evaluator;
    }

    @Override
    public Task process(F1 fn) {
      return Task.create(inputs, type, evaluator.enclose(fn), taskId);
    }

    @Override
    public  TaskBuilderC, Z> in(F0> bTask) {
      F0> bTaskSingleton = Singleton.create(bTask);
      return new BuilderC<>(
          lazyFlatten(inputs, lazyList(bTaskSingleton)),
          taskId, type,
          evaluator.curry(
              tc -> tc.evaluate(bTaskSingleton.get())));
    }

    @Override
    public  TaskBuilderC, F1, Z> ins(F0>> bTasks) {
      F0>> bTasksSingleton = Singleton.create(bTasks);
      return new BuilderC<>(
          lazyFlatten(inputs, lazyFlatten(bTasksSingleton)),
          taskId, type,
          evaluator.curry(
              tc -> bTasksSingleton.get()
                  .stream().map(tc::evaluate).collect(tc.toValueList())));
    }
  }

  private static class BuilderCV extends BaseRefs implements TaskBuilderCV {

    private final RecursiveEval evaluator;

    private BuilderCV(
        F0>> inputs, TaskId taskId, Class type, RecursiveEval evaluator) {
      super(inputs, taskId, type);
      this.evaluator = evaluator;
    }

    @Override
    public Task process(F1> code) {
      EvalClosure closure = tc -> evaluator.enclose((a) -> code.apply(tc).apply(a)).eval(tc);
      return Task.create(inputs, type, closure, taskId);
    }

    @Override
    public  TaskBuilderCV, Z> in(F0> bTask) {
      F0> bTaskSingleton = Singleton.create(bTask);
      return new BuilderCV<>(
          lazyFlatten(inputs, lazyList(bTaskSingleton)),
          taskId, type,
          evaluator.curry(
              tc -> tc.evaluate(bTaskSingleton.get())));
    }

    @Override
    public  TaskBuilderCV, F1, Z> ins(F0>> bTasks) {
      F0>> bTasksSingleton = Singleton.create(bTasks);
      return new BuilderCV<>(
          lazyFlatten(inputs, lazyFlatten(bTasksSingleton)),
          taskId, type,
          evaluator.curry(
              tc -> bTasksSingleton.get()
                  .stream().map(tc::evaluate).collect(tc.toValueList())));
    }
  }

  // #############################################################################################

  private static class Builder1 extends BaseRefs implements TaskBuilder1 {

    private final ChainingEval> evaluator;
    private final ChainingEval>> valEvaluator;

    Builder1(
        F0>> inputs,
        TaskId taskId,
        Class type,
        ChainingEval> evaluator,
        ChainingEval>> valEvaluator) {
      super(inputs, taskId, type);
      this.evaluator = evaluator;
      this.valEvaluator = valEvaluator;
    }

    @Override
    public Task process(F1 code) {
      return Task.create(inputs, type, evaluator.enclose(code), taskId);
    }

    @Override
    public Task processWithContext(F2> code) {
      EvalClosure closure = tc -> valEvaluator.enclose((a) -> code.apply(tc, a)).eval(tc);
      return Task.create(inputs, type, closure, taskId);
    }

    @Override
    public  TaskBuilder2 in(F0> bTask) {
      F0> bTaskSingleton = Singleton.create(bTask);
      return new Builder2<>(
          lazyFlatten(inputs, lazyList(bTaskSingleton)),
          taskId, type,
          evaluator.chain(tc -> {
            Value bValue = tc.evaluate(bTaskSingleton.get());
            return f2 -> bValue.map(b -> (a) -> f2.apply(a, b));
          }),
          valEvaluator.chain(tc -> {
            Value bValue = tc.evaluate(bTaskSingleton.get());
            return f2 -> bValue.map(b -> (a) -> f2.apply(a, b));
          }));
    }

    @Override
    public  TaskBuilder2, Z> ins(F0>> bTasks) {
      F0>> bTasksSingleton = Singleton.create(bTasks);
      return new Builder2<>(
          lazyFlatten(inputs, lazyFlatten(bTasksSingleton)),
          taskId, type,
          evaluator.chain(tc -> {
            Value> bListValue = bTasksSingleton.get()
                .stream().map(tc::evaluate).collect(tc.toValueList());
            return f2 -> bListValue.map(b -> (a) -> f2.apply(a, b));
          }),
          valEvaluator.chain(tc -> {
            Value> bListValue = bTasksSingleton.get()
                .stream().map(tc::evaluate).collect(tc.toValueList());
            return f2 -> bListValue.map(b -> (a) -> f2.apply(a, b));
          }));
    }
  }

  // #############################################################################################

  private static class Builder2 extends BaseRefs implements TaskBuilder2 {

    private final ChainingEval> evaluator;
    private final ChainingEval>> valEvaluator;

    Builder2(
        F0>> inputs,
        TaskId taskId,
        Class type,
        ChainingEval> evaluator,
        ChainingEval>> valEvaluator) {
      super(inputs, taskId, type);
      this.evaluator = evaluator;
      this.valEvaluator = valEvaluator;
    }

    @Override
    public Task process(F2 code) {
      return Task.create(inputs, type, evaluator.enclose(code), taskId);
    }

    @Override
    public Task processWithContext(F3> code) {
      EvalClosure closure = tc -> valEvaluator.enclose((a, b) -> code.apply(tc, a, b)).eval(tc);
      return Task.create(inputs, type, closure, taskId);
    }

    @Override
    public  TaskBuilder3 in(F0> cTask) {
      F0> cTaskSingleton = Singleton.create(cTask);
      return new Builder3<>(
          lazyFlatten(inputs, lazyList(cTaskSingleton)),
          taskId, type,
          evaluator.chain(tc -> {
            Value cValue = tc.evaluate(cTaskSingleton.get());
            return f2 -> cValue.map(c -> (a, b) -> f2.apply(a, b, c));
          }),
          valEvaluator.chain(tc -> {
            Value cValue = tc.evaluate(cTaskSingleton.get());
            return f2 -> cValue.map(c -> (a, b) -> f2.apply(a, b, c));
          }));
    }

    @Override
    public  TaskBuilder3, Z> ins(F0>> cTasks) {
      F0>> cTasksSingleton = Singleton.create(cTasks);
      return new Builder3<>(
          lazyFlatten(inputs, lazyFlatten(cTasksSingleton)),
          taskId, type,
          evaluator.chain(tc -> {
            Value> cListValue = cTasksSingleton.get()
                .stream().map(tc::evaluate).collect(tc.toValueList());
            return f3 -> cListValue.map(c -> (a, b) -> f3.apply(a, b, c));
          }),
          valEvaluator.chain(tc -> {
            Value> cListValue = cTasksSingleton.get()
                .stream().map(tc::evaluate).collect(tc.toValueList());
            return f3 -> cListValue.map(c -> (a, b) -> f3.apply(a, b, c));
          }));
    }
  }

  // #############################################################################################

  private static class Builder3 extends BaseRefs implements TaskBuilder3 {

    private final ChainingEval> evaluator;
    private final ChainingEval>> valEvaluator;

    Builder3(
        F0>> inputs,
        TaskId taskId,
        Class type,
        ChainingEval> evaluator,
        ChainingEval>> valEvaluator) {
      super(inputs, taskId, type);
      this.evaluator = evaluator;
      this.valEvaluator = valEvaluator;
    }

    @Override
    public Task process(F3 code) {
      return Task.create(inputs, type, evaluator.enclose(code), taskId);
    }

    @Override
    public Task processWithContext(F4> code) {
      EvalClosure closure = tc -> valEvaluator.enclose((a, b, c) -> code.apply(tc, a, b, c)).eval(tc);
      return Task.create(inputs, type, closure, taskId);
    }
  }

  // #############################################################################################

  /**
   * A convenience class for holding some reference. This is only so that we don't have to repeat
   * these declaration in every class above.
   */
  private static class BaseRefs {

    protected final F0>> inputs;
    protected final TaskId taskId;
    protected final Class type;

    protected BaseRefs(TaskId taskId, Class type) {
      this(Collections::emptyList, taskId, type);
    }

    protected BaseRefs(F0>> inputs, TaskId taskId, Class type) {
      this.inputs = inputs;
      this.taskId = taskId;
      this.type = type;
    }
  }

  // #############################################################################################

  private static  RecursiveEval leafEval(
      TaskId taskId,
      EvalClosure aClosure) {
    return new RecursiveEval<>(true, taskId, aClosure, taskContext -> taskContext::immediateValue);
  }

  private static  RecursiveEval, B> leafValEval(
      TaskId taskId,
      EvalClosure aClosure) {
    return new RecursiveEval<>(true, taskId, aClosure, taskContext -> val -> val);
  }

  private static  ChainingEval leafEvalFn(F1>> fClosure) {
    return new ChainingEval<>(fClosure);
  }

  private static final class RecursiveEval implements Serializable {

    private final boolean leaf;
    private final TaskId taskId;
    private final EvalClosure aClosure;
    private final F1>> contClosure;

    RecursiveEval(
        boolean leaf,
        TaskId taskId,
        EvalClosure aClosure,
        F1>> contClosure) {
      this.leaf = leaf;
      this.taskId = taskId;
      this.aClosure = aClosure;
      this.contClosure = contClosure;
    }

    public EvalClosure enclose(F1 fn) {
      return taskContext -> continuation(taskContext).apply(fn);
    }

    public  RecursiveEval, Z> curry(EvalClosure tClosure) {
      return new RecursiveEval<>(false, taskId, tClosure, this::continuation);
    }

    private F1, Value> continuation(TaskContext taskContext) {
      F1> cont = contClosure.apply(taskContext);
      Value aVal = aClosure.eval(taskContext);

      return fn -> aVal.flatMap((a) -> (leaf)
          ? taskContext.invokeProcessFn(taskId, () -> cont.apply(fn.apply(a)))
          : cont.apply(fn.apply(a)));
    }
  }

  private static final class ChainingEval implements Serializable {

    private final F1>> fClosure;

    ChainingEval(F1>> fClosure) {
      this.fClosure = fClosure;
    }

    public  EvalClosure enclose(F f) {
      //noinspection unchecked
      return taskContext -> (Value) fClosure.apply(taskContext).apply(f);
    }

    public  ChainingEval chain(F1>> mapClosure) {
      F1>> continuation = tc -> {
        F1> fng = mapClosure.apply(tc);
        F1> fnf = fClosure.apply(tc);

        return g -> fng.apply(g).flatMap(fnf::apply);
      };
      return new ChainingEval<>(continuation);
    }
  }

  private static  F1> gated(TaskId taskId, TaskContext tc, F1 f1) {
    return (a) -> tc.invokeProcessFn(taskId, () -> tc.immediateValue(f1.apply(a)));
  }

  private static  F1> gatedVal(TaskId taskId, TaskContext tc, F1> f1) {
    return (a) -> tc.invokeProcessFn(taskId, () -> f1.apply(a));
  }

  private static  EvalClosure gated(TaskId taskId, F0 code) {
    return tc -> tc.invokeProcessFn(taskId, () -> tc.value(code));
  }

  private static  EvalClosure gatedVal(TaskId taskId, F1> code) {
    return tc -> tc.invokeProcessFn(taskId, () -> code.apply(tc));
  }

  /**
   * Converts an array of {@link F0}s of {@link Task}s to a {@link F0} of a list of
   * those tasks {@link Task}s.
   *
   * It will only evaluate the functions (through calling {@link F0#get()})
   * when the returned function is invoked. Thus it retains laziness.
   *
   * @param tasks  An array of lazy evaluated tasks
   * @return A function of a list of lazily evaluated tasks
   */
  @SafeVarargs
  private static F0>> lazyList(F0>... tasks) {
    return () -> Stream.of(tasks)
        .map(F0::get)
        .collect(toList());
  }

  @SafeVarargs
  private static  F0> lazyFlatten(F0>... lists) {
    return () -> Stream.of(lists)
        .map(F0::get)
        .flatMap(List::stream)
        .collect(toList());
  }
}