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

com.github.dakusui.crest.core.Matcher Maven / Gradle / Ivy

package com.github.dakusui.crest.core;

import java.util.*;
import java.util.function.Function;
import java.util.function.Predicate;

import static java.util.Collections.singletonList;
import static java.util.Objects.requireNonNull;
import static java.util.stream.Collectors.toList;

public interface Matcher {
  boolean matches(T value, Assertion session);

  List describeExpectation(Assertion session);

  List describeMismatch(T value, Assertion session);

  interface Composite extends Matcher {
    abstract class Base implements Composite {
      private final List> children;
      private final boolean          topLevel;

      @SuppressWarnings("unchecked")
      Base(boolean topLevel, List> children) {
        this.children = (List>) Collections.unmodifiableList((List) requireNonNull(children));
        this.topLevel = topLevel;
      }

      @Override
      public boolean matches(T value, Assertion session) {
        boolean ret = first();
        for (Matcher eachChild : children())
          ret = op(ret, eachChild.matches(value, session));
        return ret && session.exceptions().isEmpty();
      }

      @Override
      public List describeExpectation(Assertion session) {
        return new LinkedList() {{
          add(String.format("%s:[", name()));
          children().forEach(
              (Matcher eachChild) -> {
                List formattedExpectation = eachChild.describeExpectation(session);
                if (formattedExpectation.isEmpty())
                  return;
                if (formattedExpectation.size() == 1)
                  add(String.format("  %s", eachChild.describeExpectation(session).get(0)));
                else {
                  addAll(indent(eachChild.describeExpectation(session)));
                }
              }
          );
          add("]");
        }};
      }

      @Override
      public List describeMismatch(T value, Assertion session) {
        return new LinkedList() {{
          if (topLevel)
            add(String.format("when x=%s; then %s:[", InternalUtils.formatValue(value), name()));
          else
            add(String.format("%s:[", name()));
          for (Matcher eachChild : children()) {
            if (eachChild.matches(value, session))
              addAll(indent(eachChild.describeExpectation(session)));
            else
              addAll(indent(eachChild.describeMismatch(value, session)));
          }
          add(String.format("]->%s", matches(value, session)));
          session.exceptions().forEach(
              e -> {
                add(e.getMessage());
                addAll(
                    indent(Arrays.stream(e.getStackTrace()).map(
                        StackTraceElement::toString
                    ).collect(toList())));
              }
          );
        }};
      }

      List indent(List in) {
        return in.stream().map(s -> "  " + s).collect(toList());
      }

      List> children() {
        return this.children;
      }

      abstract protected String name();

      abstract protected boolean first();

      abstract protected boolean op(boolean current, boolean next);
    }
  }

  interface Conjunctive extends Composite {
    @SuppressWarnings("unchecked")
    static  Matcher create(boolean topLevel, List> matchers) {
      return new Conjunctive.Base(topLevel, matchers) {
        @Override
        protected String name() {
          return "and";
        }

        @Override
        protected boolean first() {
          return true;
        }

        @Override
        protected boolean op(boolean current, boolean next) {
          return current && next;
        }
      };
    }
  }

  interface Disjunctive extends Composite {
    @SuppressWarnings("unchecked")
    static  Matcher create(boolean topLevel, List> matchers) {
      return new Composite.Base(topLevel, matchers) {

        @Override
        protected String name() {
          return "or";
        }

        @Override
        protected boolean first() {
          return false;
        }

        @Override
        protected boolean op(boolean current, boolean next) {
          return current || next;
        }
      };
    }
  }

  interface Leaf extends Matcher {
    static  Matcher create(Predicate p, Function function) {
      return new Matcher() {
        @SuppressWarnings("unchecked")
        @Override
        public boolean matches(I value, Assertion session) {
          return session.test((Predicate) p, session.apply(function, value));
        }

        @Override
        public List describeExpectation(Assertion session) {
          return singletonList(InternalUtils.formatExpectation(p, function));
        }

        @Override
        public List describeMismatch(I value, Assertion session) {
          @SuppressWarnings("unchecked") Optional exception = session.thrownExceptionFor((Predicate) p, (I) session.apply(function, value));
          return exception.map(throwable -> singletonList(String.format(
              "%s failed with %s(%s)",
              InternalUtils.formatExpectation(p, function),
              throwable.getClass().getCanonicalName(),
              throwable.getMessage()
          ))).orElseGet(() -> singletonList(String.format(
              "%s was false because %s=%s does not satisfy it",
              InternalUtils.formatExpectation(p, function),
              InternalUtils.formatFunction(function, "x"),
              InternalUtils.formatValue(session.apply(function, value))
          )));
        }
      };
    }
  }
}