Matcher.java

package com.github.dakusui.pcond.core.fluent;

import com.github.dakusui.pcond.forms.Functions;
import com.github.dakusui.pcond.forms.Predicates;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;

import static com.github.dakusui.pcond.internals.InternalChecks.requireState;
import static java.util.Collections.unmodifiableList;
import static java.util.Objects.requireNonNull;
import static java.util.stream.Collectors.toList;

public interface Matcher<
    M extends Matcher<M, T, R>,
    T,
    R> {
  M allOf();

  M anyOf();

  Predicate<T> toPredicate();

  Function<T, R> transformFunction();

  abstract class Base<
      M extends Matcher<M, T, R>,
      T,
      R> implements Matcher<M, T, R> {
    private final Function<T, R> transformFunction;
    private final Supplier<T>    baseValue;

    private final List<Function<Matcher<?, R, R>, Predicate<R>>> childPredicates = new LinkedList<>();
    private       Matcher.JunctionType                           junctionType;

    private Predicate<T> builtPredicate;

    protected Base(Supplier<T> baseValue, Function<T, R> transformFunction) {
      this.transformFunction = requireNonNull(transformFunction);
      this.baseValue = requireNonNull(baseValue);
      this.allOf();
    }

    @Override
    public M allOf() {
      return junctionType(Matcher.JunctionType.CONJUNCTION);
    }

    @Override
    public M anyOf() {
      return junctionType(Matcher.JunctionType.DISJUNCTION);
    }

    @Override
    public Predicate<T> toPredicate() {
      if (this.builtPredicate == null)
        this.builtPredicate = buildPredicate();
      return this.builtPredicate;
    }

    protected M addPredicate(Function<Matcher<?, R, R>, Predicate<R>> clause) {
      this.childPredicates.add(requireNonNull(clause));
      return me();
    }

    public R value() {
      return this.transformFunction.apply(this.baseValue());
    }

    /* protected */
    public Function<T, R> transformFunction() {
      return this.transformFunction;
    }

    protected boolean hasNoChild() {
      return this.childPredicates.isEmpty();
    }

    protected List<Function<Matcher<?, R, R>, Predicate<R>>> childPredicates() {
      return unmodifiableList(this.childPredicates);
    }

    @SuppressWarnings("unchecked")
    protected M me() {
      return (M) this;
    }

    /**
     * Override this method so that it returns extending class.
     *
     * @return A rebased transformer.
     */
    protected abstract Matcher<?, R, R> rebase();

    /* protected */
    protected T baseValue() {
      return this.baseValue.get();
    }

    private M junctionType(Matcher.JunctionType junctionType) {
      requireState(this, v -> childPredicates.isEmpty(), v -> "Child predicate(s) are already added.: <" + this + ">");
      this.junctionType = requireNonNull(junctionType);
      return me();
    }

    @SuppressWarnings("unchecked")
    private Predicate<T> buildPredicate() {
      Predicate<R> ret;
      requireState(this, v -> !v.childPredicates.isEmpty(), (v) -> "No child has been added yet.: <" + v + ">");
      if (this.childPredicates.size() == 1)
        ret = childPredicates.get(0).apply(rebase());
      else {
        ret = this.junctionType.connect(
            new ArrayList<>(this.childPredicates)
                .stream()
                .map(each -> each.apply(rebase()))
                .collect(toList()));
      }
      if (Objects.equals(transformFunction, Functions.identity()))
        return (Predicate<T>) ret;
      return Predicates.transform(transformFunction).check("THEN", ret);
    }
  }

  enum JunctionType {
    CONJUNCTION {
      @SuppressWarnings("unchecked")
      @Override
      public <T> Predicate<T> connect(List<Predicate<T>> predicates) {
        return Predicates.allOf(predicates.toArray(new Predicate[0]));
      }
    },
    DISJUNCTION {
      @SuppressWarnings("unchecked")
      @Override
      public <T> Predicate<T> connect(List<Predicate<T>> predicates) {
        return Predicates.anyOf(predicates.toArray(new Predicate[0]));
      }
    };

    public abstract <T> Predicate<T> connect(List<Predicate<T>> predicates);
  }
}