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 com.github.dakusui.crest.functions.TransformingPredicate;

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")
      public 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.size() == 1)
                  add(String.format("  %s", formattedExpectation.get(0)));
                else {
                  addAll(indent(formattedExpectation));
                }
              }
          );
          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 Negative extends Composite {
    static  Matcher create(Matcher matcher) {
      return new Composite.Base(true, Collections.singletonList(matcher)) {
        @Override
        protected String name() {
          return "not";
        }

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

        @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(formatExpectation(p, function));
        }

        @SuppressWarnings("unchecked")
        @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)",
              formatExpectation(p, function),
              throwable.getClass().getCanonicalName(),
              throwable.getMessage()
          ))).orElseGet(() -> {
            if (p instanceof TransformingPredicate) {
              TransformingPredicate pp = (TransformingPredicate) p;
              return singletonList(String.format(
                  "%s%s was not met because %s=%s; %s=%s",
                  formatExpectation(p, function),
                  pp.name().isPresent() ? "," : "",
                  InternalUtils.formatFunction(pp.function(), InternalUtils.formatFunction(function, "x")),
                  InternalUtils.formatValue(session.apply(pp.function(), session.apply(function, value))),
                  InternalUtils.formatFunction(function, "x"),
                  InternalUtils.formatValue(session.apply(function, value))
              ));
            }
            return singletonList(String.format(
                "%s was not met because %s=%s",
                formatExpectation(p, function),
                InternalUtils.formatFunction(function, "x"),
                InternalUtils.formatValue(session.apply(function, value))));
          });
        }

        String formatExpectation(Predicate p, Function function) {
          if (p instanceof TransformingPredicate) {
            TransformingPredicate pp = (TransformingPredicate) p;
            return String.format("%s%s %s",
                pp.name().isPresent() ?
                    pp.name().get() + ", i.e. " :
                    "",
                InternalUtils.formatFunction(pp.function(), InternalUtils.formatFunction(function, "x")), pp.predicate());
          } else
            return String.format("%s %s", InternalUtils.formatFunction(function, "x"), p.toString());
        }
      };
    }
  }
}