Validator.java

package com.github.dakusui.pcond.validator;

import com.github.dakusui.pcond.core.*;
import com.github.dakusui.pcond.forms.Predicates;

import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Properties;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;

import static com.github.dakusui.pcond.internals.InternalUtils.toEvaluableIfNecessary;
import static com.github.dakusui.pcond.validator.Validator.Configuration.Utils.*;
import static java.lang.String.format;
import static java.util.Collections.emptyList;

/**
 * An interface of a policy for behaviours on 'contract violations'.
 */
public interface Validator {
  /**
   * A constant field that holds the default provider instance.
   */
  ThreadLocal<Validator> INSTANCE = ThreadLocal.withInitial(() -> create(loadPcondProperties()));


  /**
   * Returns a configuration object that determines behaviors of this object.
   *
   * @return A configuration object.
   */
  Configuration configuration();

  /**
   * Returns a provider instance created from a given `Properties` object.
   * This method reads the value for the FQCN of this class (`com.github.dakusui.pcond.provider.AssertionProvider`) and creates an instance of a class specified by the value.
   * If the value is not set, this value instantiates an object of `DefaultAssertionProvider` and returns it.
   *
   * @param properties A {@code Properties} object from which an {@code AssertionProvider} is created
   * @return Created provider instance.
   */
  static Validator create(Properties properties) {
    return new Impl(configurationFromProperties(properties));
  }

  /**
   * Checks a value if it is {@code null} or not.
   * If it is not a {@code null}, this method returns the given value itself.
   *
   * @param value The given value.
   * @param <T>   The type of the value.
   * @return The {@code value}.
   */
  default <T> T requireNonNull(T value) {
    return require(value, Predicates.isNotNull(), configuration().exceptionComposer().forRequire()::exceptionForNonNullViolation);
  }

  /**
   * Checks a value if it meets a requirement specified by {@code cond}.
   * If it does, the value itself will be returned.
   *
   * @param value The value to be checked.
   * @param cond  The requirement to check the {@code value}.
   * @param <T>   The type of the value.
   * @return The value.
   */
  default <T> T requireArgument(T value, Predicate<? super T> cond) {
    return require(value, cond, configuration().exceptionComposer().forRequire()::exceptionForIllegalArgument);
  }

  /**
   * Checks a value if it meets a requirement specified by {@code cond}.
   * If it does, the value itself will be returned.
   *
   * @param value The value to be checked.
   * @param cond  The requirement to check the {@code value}.
   * @param <T>   The type of the value.
   * @return The value.
   */
  default <T> T requireState(T value, Predicate<? super T> cond) {
    return require(value, cond, configuration().exceptionComposer().forRequire()::exceptionForIllegalState);
  }

  /**
   * A method to check if a given `value` satisfies a precondition given as `cond`.
   * If the `cond` is satisfied, the `value` itself will be returned.
   * Otherwise, an exception returned by `configuration().exceptionComposer().forRequire().exceptionForGeneralViolation(String)`
   * will be thrown.
   *
   * @param value A value to be checked.
   * @param cond  A condition to check if `value` satisfies.
   * @param <T>   The of the `value`.
   * @return The `value`, if `cond` is satisfied.
   */
  default <T> T require(T value, Predicate<? super T> cond) {
    return require(value, cond, msg -> configuration().exceptionComposer().forRequire().exceptionForGeneralViolation(msg));
  }

  /**
   * A method to check if a given `value` satisfies a precondition given as `cond`.
   * If the `cond` is satisfied, the `value` itself will be returned.
   * Otherwise, an exception created by `exceptionFactory` is thrown.
   *
   * @param value            A value to be checked.
   * @param cond             A condition to check if `value` satisfies.
   * @param exceptionFactory A function to create an exception thrown when `cond`
   *                         is not satisfied by `value`.
   * @param <T>              The of the `value`.
   * @return The `value`, if `cond` is satisfied.
   */
  default <T> T require(T value, Predicate<? super T> cond, Function<String, Throwable> exceptionFactory) {
    return checkValueAndThrowIfFails(
        value,
        cond,
        this.configuration().messageComposer()::composeMessageForPrecondition,
        explanation -> exceptionFactory.apply(explanation.toString()));
  }

  /**
   * Validates the given `value`.
   * If the value satisfies a condition `cond`, the value itself will be returned.
   * Otherwise, an exception created by `forValidate.exceptionForGeneralViolation()`
   * will be thrown.
   * This method is intended to be used by {@code Validates#validate(Object, Predicate, Function)}
   * method in `valid8j` library.
   *
   * @param value       The value to be checked.
   * @param cond        A condition to validate the `value`.
   * @param forValidate An exception composer for "validate" methods.
   * @param <T>         The type of the value.
   * @return The value itself.
   */
  default <T> T validate(T value, Predicate<? super T> cond, ExceptionComposer.ForValidate forValidate) {
    return validate(value, cond, forValidate::exceptionForGeneralViolation);
  }

  /**
   * Validates the given `value`.
   * If the value is not `null`, the value itself will be returned.
   * Otherwise, an exception created by `forValidate.exceptionForGeneralViolation()`
   * will be thrown.
   * This method is intended to be used by {@code Validates#validateNonNull(Object)}
   * method in valid8j library.
   *
   * @param value       The value to be checked.
   * @param forValidate An exception composer for "validate" methods.
   * @param <T>         The type of the value.
   * @return The value itself.
   */
  default <T> T validateNonNull(T value, ExceptionComposer.ForValidate forValidate) {
    return validate(value, Predicates.isNotNull(), forValidate::exceptionForNonNullViolation);
  }

  /**
   * Validates the given argument variable `value`.
   * If the value satisfies a condition `cond` for checking an argument variable, the value itself will be returned.
   * Otherwise, an exception created by `forValidate.exceptionForIllegalArgument()`
   * will be thrown.
   * This method is intended to be used by {@code Validates#validateArgument(Object, Predicate)}
   * method in `valid8j` library.
   *
   * @param value       The value to be checked.
   * @param cond        A condition to validate the `value`.
   * @param forValidate An exception composer for "validate" methods.
   * @param <T>         The type of the value.
   * @return The value itself.
   */
  default <T> T validateArgument(T value, Predicate<? super T> cond, ExceptionComposer.ForValidate forValidate) {
    return validate(value, cond, forValidate::exceptionForIllegalArgument);
  }

  /**
   * Validates the given state variable `value`.
   * If the value satisfies a condition `cond` for checking a state, the value itself will be returned.
   * Otherwise, an exception created by `forValidate.exceptionForIllegalState()`
   * will be thrown.
   * This method is intended to be used by {@code Validates#validateState(Object, Predicate)}
   * method in valid8j library.
   *
   * @param value       The value to be checked.
   * @param cond        A condition to validate the `value`.
   * @param forValidate An exception composer for "validate" methods.
   * @param <T>         The type of the value.
   * @return The value itself.
   */
  default <T> T validateState(T value, Predicate<? super T> cond, ExceptionComposer.ForValidate forValidate) {
    return validate(value, cond, forValidate::exceptionForIllegalState);
  }

  /**
   * Validates the given variable `value`.
   * If the value satisfies a condition `cond`, the value itself will be returned.
   * Otherwise, an exception created by `exceptionFactory` will be thrown.
   * This method is intended to be used by {@code Validates#validate(Object, Predicate, Function)}
   * method in valid8j library.
   *
   * @param value            The value to be checked.
   * @param cond             A condition to validate the `value`.
   * @param exceptionFactory A function to create an exception when the `cond` is not satisfied.
   * @param <T>              The type of the value.
   * @return The value itself.
   */
  default <T> T validate(T value, Predicate<? super T> cond, Function<String, Throwable> exceptionFactory) {
    return validate_2(value, cond, explanation -> exceptionFactory.apply(explanation.toString()));
  }

  default <T> T validate_2(T value, Predicate<? super T> cond, ExceptionFactory<Throwable> exceptionFactory) {
    return checkValueAndThrowIfFails(
        value,
        cond,
        configuration().messageComposer()::composeMessageForValidation,
        exceptionFactory);
  }

  /**
   * Checks a value if it is not `null`.
   * If it is not `null`, the value itself will be returned.
   * If it is, an exception created by `configuration().exceptionComposer().forEnsure().exceptionForNonNullViolation()` will be thrown.
   * This method is intended for ensuring a "post-condition".
   *
   * @param value The value to be checked.
   * @param <T>   The type of the value.
   * @return The value.
   */
  default <T> T ensureNonNull(T value) {
    return ensure(value, Predicates.isNotNull(), configuration().exceptionComposer().forEnsure()::exceptionForNonNullViolation);
  }

  /**
   * Checks a value if it meets a requirement specified by {@code cond}.
   * If it does, the value itself will be returned.
   * If it does not, an exception created by `configuration().exceptionComposer().forEnsure().exceptionForIllegalState()` will be thrown.
   * This method is intended for ensuring a "post-condition" of a state.
   *
   * @param value The value to be checked.
   * @param cond  The requirement to check the {@code value}.
   * @param <T>   The type of the value.
   * @return The value.
   */
  default <T> T ensureState(T value, Predicate<? super T> cond) {
    return ensure(value, cond, configuration().exceptionComposer().forEnsure()::exceptionForIllegalState);
  }

  /**
   * Checks a value if it meets a requirement specified by {@code cond}.
   * If it does, the value itself will be returned.
   * If it does not, an exception created by `configuration().exceptionComposer().forEnsure().exceptionForGeneralViolation()` will be thrown.
   * This method is intended for ensuring a "post-condition".
   *
   * @param value The value to be checked.
   * @param cond  The requirement to check the {@code value}.
   * @param <T>   The type of the value.
   * @return The value.
   */
  default <T> T ensure(T value, Predicate<? super T> cond) {
    return ensure(value, cond, msg -> configuration().exceptionComposer().forEnsure().exceptionForGeneralViolation(msg));
  }

  /**
   * Checks a value if it meets a requirement specified by {@code cond}.
   * If it does, the value itself will be returned.
   * If it does not, an exception created by `exceptionComposer` will be thrown.
   * This method is intended for ensuring a "post-condition".
   *
   * @param value             The value to be checked.
   * @param cond              The requirement to check the {@code value}.
   * @param exceptionComposer A function to create an exception to be thrown when
   *                          `cond` is not met.
   * @param <T>               The type of the value.
   * @return The value.
   */
  default <T> T ensure(T value, Predicate<? super T> cond, Function<String, Throwable> exceptionComposer) {
    return checkValueAndThrowIfFails(
        value,
        cond,
        configuration().messageComposer()::composeMessageForPostcondition,
        explanation -> exceptionComposer.apply(explanation.toString()));
  }

  /**
   * A method to check if a `value` satisfies a predicate `cond`.
   *
   * This method is intended to be used by {@code Assertions#that(Object, Predicate)} in valid8j library.
   * If the condition is not satisfied, an exception created by `this.exceptionComposer().forAssert().exceptionInvariantConditionViolation()`
   * method will be thrown.
   *
   * @param value A value to be checked.
   * @param cond  A condition to check the `value`.
   * @param <T>   The type of `value`.
   */
  default <T> void checkInvariant(T value, Predicate<? super T> cond) {
    checkValueAndThrowIfFails(
        value,
        cond,
        configuration().messageComposer()::composeMessageForAssertion,
        explanation -> configuration().exceptionComposer().forAssert().exceptionInvariantConditionViolation(explanation.toString()));
  }

  /**
   * A method to check if a `value` satisfies a predicate `cond`.
   *
   * This method is intended to be used by {@code Assertions#precondition(Object, Predicate)} in valid8j library.
   * If the condition is not satisfied, an exception created by `this.exceptionComposer().forAssert().exceptionPreconditionViolation()`
   * method will be thrown.
   *
   * @param value A value to be checked.
   * @param cond  A condition to check the `value`.
   * @param <T>   The type of `value`.
   */
  default <T> void checkPrecondition(T value, Predicate<? super T> cond) {
    checkValueAndThrowIfFails(
        value,
        cond,
        configuration().messageComposer()::composeMessageForPrecondition,
        explanation -> configuration().exceptionComposer().forAssert().exceptionPreconditionViolation(explanation.toString()));
  }

  /**
   * A method to check if a `value` satisfies a predicate `cond`.
   *
   * This method is intended to be used by {@code Assertions#postcondition(Object, Predicate)} in valid8j library.
   * If the condition is not satisfied, an exception created by `this.exceptionComposer().forAssert().exceptionPostconditionViolation()`
   * method will be thrown.
   *
   * @param value A value to be checked.
   * @param cond  A condition to check the `value`.
   * @param <T>   The type of `value`.
   */
  default <T> void checkPostcondition(T value, Predicate<? super T> cond) {
    checkValueAndThrowIfFails(
        value,
        cond,
        configuration().messageComposer()::composeMessageForPostcondition,
        explanation -> configuration().exceptionComposer().forAssert().exceptionPostconditionViolation(explanation.toString()));
  }

  /**
   * Executes a test assertion for a given `value` using a predicate `cond`.
   * If the `cond` is not satisfied by the `value`, an exception created by `configuration().messageComposer().composeMessageForAssertion()`
   * will be thrown.
   *
   * @param value A value to be checked.
   * @param cond  A predicate to check a given `value`.
   * @param <T>   The type of the `value`.
   */
  default <T> void assertThat(T value, Predicate<? super T> cond) {
    checkValueAndThrowIfFails(
        value,
        cond,
        configuration().messageComposer()::composeMessageForAssertion,
        explanation -> configuration().exceptionComposer().forAssertThat().testFailedException(explanation, configuration().reportComposer()));
  }

  /**
   * Executes a test assumption check for a given `value` using a predicate `cond`.
   * If the `cond` is not satisfied by the `value`, an exception created by `configuration().messageComposer().composeMessageForAssertion()`
   * will be thrown.
   *
   * @param value A value to be checked.
   * @param cond  A predicate to check a given `value`.
   * @param <T>   The type of the `value`.
   */
  default <T> void assumeThat(T value, Predicate<? super T> cond) {
    checkValueAndThrowIfFails(
        value,
        cond,
        configuration().messageComposer()::composeMessageForAssertion,
        explanation -> configuration().exceptionComposer().forAssertThat().testSkippedException(explanation, configuration().reportComposer()));
  }

  /**
   * The core method of the `ValueChecker`.
   * This method checks if the given `evaluationContext` satisfies a condition, passed as `cond`.
   * If it does, the `evaluationContext` itself will be returned.
   * If not, an appropriate message will be composed based on the `evaluationContext` and `cond` by the `messageComposerFunction`.
   * Internally in this method, an `Explanation` of the failure is created by a {@link ReportComposer}
   * object returned by `configuration().reportComposer()` method.
   * The `Explanation` is passed to the `exceptionComposerFunction` and the exception
   * created by the function will be thrown.
   *
   * @param <T>                       The type of the `evaluationContext`.
   * @param value                     A value to be checked.
   * @param cond                      A predicate that checks the `evaluationContext`.
   * @param messageComposerFunction   A function that composes an error message from the `evaluationContext` and the predicate `cond`.
   * @param exceptionComposerFunction A function that creates an exception from a failure report created inside this method.
   * @return The `evaluationContext` itself.
   */
  @SuppressWarnings("unchecked")
  default <T> T checkValueAndThrowIfFails(
      T value,
      Predicate<? super T> cond,
      BiFunction<T, Predicate<? super T>, String> messageComposerFunction,
      ExceptionFactory<Throwable> exceptionComposerFunction) {
    ValueHolder<T> valueHolder = ValueHolder.forValue(value);
    Evaluable<T> evaluable = toEvaluableIfNecessary(cond);
    EvaluableIo<T, Evaluable<T>, Boolean> evaluableIo = new EvaluableIo<>(valueHolder, EvaluationContext.resolveEvaluationEntryType(evaluable), evaluable);
    EvaluationContext<T> evaluationContext = new EvaluationContext<>();
    if (this.configuration().useEvaluator() && cond instanceof Evaluable) {
      Evaluator evaluator = Evaluator.create();
      ((Evaluable<T>) cond).accept(evaluableIo, evaluationContext, evaluator);
      if (evaluableIo.output().isValueReturned() && Objects.equals(true, evaluableIo.output().value()))
        return value;
      List<EvaluationEntry> entries = evaluationContext.resultEntries();
      throw exceptionComposerFunction.create(configuration()
          .reportComposer()
          .composeExplanation(
              messageComposerFunction.apply(value, cond),
              entries
          ));
    } else {
      if (!cond.test(valueHolder.returnedValue()))
        throw exceptionComposerFunction.create(configuration()
            .reportComposer()
            .composeExplanation(
                messageComposerFunction.apply(value, cond),
                emptyList()
            ));
      return value;
    }
  }

  static Validator instance() {
    return INSTANCE.get();
  }

  static void reconfigure(Consumer<Configuration.Builder> configurator) {
    Configuration.Builder b = instance().configuration().parentBuilder();
    reconfigure(configurator, b);
  }

  static void reconfigure(Consumer<Configuration.Builder> configurator, Properties properties) {
    Configuration.Builder b = Configuration.Builder.fromProperties(properties);
    reconfigure(configurator, b);
  }

  static void reconfigure(Consumer<Configuration.Builder> configurator, Configuration.Builder b) {
    Objects.requireNonNull(configurator).accept(b);
    INSTANCE.set(new Impl(b.build()));
  }

  static void resetToDefault() {
    reconfigure(b -> {
    });
  }

  static Configuration configurationFromProperties(Properties properties) {
    return Configuration.Builder.fromProperties(properties).build();
  }

  interface ExceptionFactory<E extends Throwable> extends Function<Explanation, E> {
    default RuntimeException create(Explanation explanation) {
      return createException(this, explanation);
    }

    static RuntimeException createException(ExceptionFactory<?> exceptionFactory, Explanation explanation) {
      Throwable t = exceptionFactory.apply(explanation);
      if (t instanceof Error)
        throw (Error) t;
      if (t instanceof RuntimeException)
        throw (RuntimeException) t;
      throw new AssertionError(format("Checked exception(%s) cannot be used for validation.", t.getClass()), t);
    }
  }

  interface Configuration {
    /**
     * When `com.github.dakusui.pcond.debug` is not `true`, it is assumed that those methods in this interface return `false`.
     */
    interface Debugging {
      default boolean suppressSquashing() {
        return true;
      }

      default boolean enableDebugLog() {
        return true;
      }

      default boolean showEvaluableDetail() {
        return true;
      }

      default boolean reportIgnoredEntries() {
        return true;
      }

      default boolean passThroughComparisonFailure() {
        return true;
      }
    }

    int summarizedStringLength();

    boolean useEvaluator();

    /**
     * Returns a message composer, which is responsible for composing an appropriate message for
     * a context.
     *
     * @return A message composer.
     */
    MessageComposer messageComposer();

    /**
     * Returns a report composer, which is responsible for composing an appropriate "report" for
     * a context.
     *
     * @return A report composer
     */
    ReportComposer reportComposer();

    /**
     * Returns an exception composer, which is responsible for creating an exception
     * object of an appropriate type for a context.
     *
     * @return An exception composer.
     */
    ExceptionComposer exceptionComposer();

    Optional<Debugging> debugging();

    Configuration.Builder parentBuilder();

    enum Utils {
      ;

      @SuppressWarnings("unchecked")
      static <E> E instantiate(@SuppressWarnings("unused") Class<E> baseClass, String className) {
        try {
          return (E) Class.forName(className).newInstance();
        } catch (InstantiationException | IllegalAccessException |
                 ClassNotFoundException e) {
          throw new RuntimeException(e);
        }
      }

      public static Properties loadPcondProperties() {
        try {
          Properties ret = new Properties();
          System.getProperties().forEach((k, v) -> {
            String key = Objects.toString(k);
            String value = Objects.toString(v);
            String prefix = "com.github.dakusui.pcond.";
            if (key.startsWith(prefix)) {
              ret.put(key.replace(prefix, ""), value);
            }
          });
          try (InputStream inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("pcond.properties")) {
            if (inputStream == null)
              return ret;
            ret.load(inputStream);
            return ret;
          }
        } catch (IOException e) {
          throw new RuntimeException(e);
        }
      }
    }

    class Builder implements Cloneable {
      boolean useEvaluator;
      int     summarizedStringLength;


      MessageComposer messageComposer;
      ReportComposer  reportComposer;
      private ExceptionComposer.ForRequire       exceptionComposerForRequire;
      private ExceptionComposer.ForEnsure        exceptionComposerForEnsure;
      private ExceptionComposer.ForValidate      defaultExceptionComposerForValidate;
      private ExceptionComposer.ForAssertion     exceptionComposerForAssert;
      private ExceptionComposer.ForTestAssertion exceptionComposerForTestFailures;

      public Builder() {
      }


      public Builder useEvaluator(boolean useEvaluator) {
        this.useEvaluator = useEvaluator;
        return this;
      }

      public Builder summarizedStringLength(int summarizedStringLength) {
        this.summarizedStringLength = summarizedStringLength;
        return this;
      }

      public Builder exceptionComposerForRequire(ExceptionComposer.ForRequire exceptionComposerForRequire) {
        this.exceptionComposerForRequire = exceptionComposerForRequire;
        return this;
      }

      public Builder exceptionComposerForEnsure(ExceptionComposer.ForEnsure exceptionComposerForEnsure) {
        this.exceptionComposerForEnsure = exceptionComposerForEnsure;
        return this;
      }

      public Builder defaultExceptionComposerForValidate(ExceptionComposer.ForValidate exceptionComposerForValidate) {
        this.defaultExceptionComposerForValidate = exceptionComposerForValidate;
        return this;
      }

      public Builder exceptionComposerForAssert(ExceptionComposer.ForAssertion exceptionComposerForAssert) {
        this.exceptionComposerForAssert = exceptionComposerForAssert;
        return this;
      }

      public Builder exceptionComposerForAssertThat(ExceptionComposer.ForTestAssertion exceptionComposerForAssertThat) {
        this.exceptionComposerForTestFailures = exceptionComposerForAssertThat;
        return this;
      }

      public Builder messageComposer(MessageComposer messageComposer) {
        this.messageComposer = messageComposer;
        return this;
      }

      public Builder reportComposer(ReportComposer reportComposer) {
        this.reportComposer = reportComposer;
        return this;
      }

      public Builder useOpentest4J() {
        this.exceptionComposerForTestFailures = new ExceptionComposer.ForTestAssertion.Opentest4J();
        return this;
      }

      public Configuration build() {
        if (!isClassPresent("org.junit.ComparisonFailure"))
          this.useOpentest4J();
        return new Configuration() {
          private final Debugging debugging = new Debugging() {
          };

          private final ExceptionComposer exceptionComposer = new ExceptionComposer.Impl(
              exceptionComposerForRequire,
              exceptionComposerForEnsure,
              defaultExceptionComposerForValidate,
              exceptionComposerForAssert,
              exceptionComposerForTestFailures
          );

          @Override
          public int summarizedStringLength() {
            return Builder.this.summarizedStringLength;
          }

          @Override
          public boolean useEvaluator() {
            return Builder.this.useEvaluator;
          }

          /**
           * Returns an exception composer, which is responsible for creating an exception
           * object of an appropriate type for a context.
           *
           * @return An exception composer.
           */
          public ExceptionComposer exceptionComposer() {
            return this.exceptionComposer;
          }

          @Override
          public Optional<Debugging> debugging() {
            if (Boolean.parseBoolean(System.getProperty("com.github.dakusui.pcond.debug"))) {
              return Optional.of(this.debugging);
            }
            return Optional.empty();
          }

          @Override
          public MessageComposer messageComposer() {
            return Builder.this.messageComposer;
          }

          @Override
          public ReportComposer reportComposer() {
            return Builder.this.reportComposer;
          }

          @Override
          public Builder parentBuilder() {
            return Builder.this.clone();
          }
        };
      }

      private static boolean isClassPresent(String s) {
        try {
          Class.forName(s);
          return true;
        } catch (ClassNotFoundException e) {
          return false;
        }
      }

      @Override
      public Builder clone() {
        try {
          return (Builder) super.clone();
        } catch (CloneNotSupportedException e) {
          throw new AssertionError();
        }
      }

      static Builder fromProperties(Properties properties) {
        return new Builder()
            .useEvaluator(Boolean.parseBoolean(properties.getProperty("useEvaluator", "true")))
            .summarizedStringLength(Integer.parseInt(properties.getProperty("summarizedStringLength", "40")))
            .exceptionComposerForRequire(instantiate(ExceptionComposer.ForRequire.class, properties.getProperty("exceptionComposerForRequire", "com.github.dakusui.pcond.validator.ExceptionComposer$ForRequire$Default")))
            .exceptionComposerForEnsure(instantiate(ExceptionComposer.ForEnsure.class, properties.getProperty("exceptionComposerForEnsure", "com.github.dakusui.pcond.validator.ExceptionComposer$ForEnsure$Default")))
            .defaultExceptionComposerForValidate(instantiate(ExceptionComposer.ForValidate.class, properties.getProperty("defaultExceptionComposerForValidate", "com.github.dakusui.pcond.validator.ExceptionComposer$ForValidate$Default")))
            .exceptionComposerForAssert(instantiate(ExceptionComposer.ForAssertion.class, properties.getProperty("exceptionComposerForAssert", "com.github.dakusui.pcond.validator.ExceptionComposer$ForAssertion$Default")))
            .exceptionComposerForAssertThat(instantiate(ExceptionComposer.ForTestAssertion.class, properties.getProperty("exceptionComposerForTestFailures", "com.github.dakusui.pcond.validator.ExceptionComposer$ForTestAssertion$JUnit4")))
            .messageComposer(instantiate(MessageComposer.class, properties.getProperty("messageComposer", "com.github.dakusui.pcond.validator.MessageComposer$Default")))
            .reportComposer(instantiate(ReportComposer.class, properties.getProperty("reportComposer", "com.github.dakusui.pcond.validator.ReportComposer$Default")));
      }
    }
  }

  class Impl implements Validator {

    private final Configuration configuration;

    public Impl(Configuration configuration) {
      this.configuration = Objects.requireNonNull(configuration);
    }

    @Override
    public Configuration configuration() {
      return this.configuration;
    }
  }
}