Parameter.java

package com.github.dakusui.jcunitx.metamodel;

import com.github.dakusui.jcunitx.core.AArray;
import com.github.dakusui.jcunitx.factorspace.Constraint;
import com.github.dakusui.jcunitx.factorspace.Factor;
import com.github.dakusui.jcunitx.factorspace.FactorSpace;

import java.util.LinkedList;
import java.util.List;
import java.util.Optional;

import static java.util.Objects.requireNonNull;

/**
 * A class that models a test input parameter or parameters.
 *
 * @param <T> Type of values held by this class.
 */
public interface Parameter<T> {
  /**
   * Returns a name of this parameter.
   *
   * @return A name of this parameter.
   */
  String getName();

  /**
   * Encodes this parameter into factors and constraints.
   *
   * @return Encoded factors and constraints.
   */
  FactorSpace toFactorSpace();

  /**
   * Compose a value of this parameter from a tuple.
   *
   * @param tuple an internal representation of a value of this parameter.
   * @return A value of this parameter.
   */
  T composeValue(AArray tuple);

  /**
   * Decomposes a value into an associative-array.
   *
   * @param value A value to be decomposed.
   * @return An associative-array that represents the `value`.
   */
  // TODO: Perhaps, we do not need to return `Optional` from this method, but we can just return the `Aarray`.
  Optional<AArray> decomposeValue(T value);

  /**
   * Returns a list of "known values" of this parameter.
   * In case `Simple` parameter model, the values given on its construction are returned.
   * For non-simple parameters, those are the ones the user directly specifies.
   * To implement "seeding" feature such values are important, because users are interested in the user level representation,
   * but not in the internal representation.
   *
   * @return A list of known values of this parameter.
   */
  List<T> getKnownValues();

  Descriptor<T> descriptor();

  /**
   * A factory interface for `Parameter`.
   *
   * @param <T> The type of the parameter values (user-facing values) created by this factory.
   */
  interface Descriptor<T> {
    /**
     * Add an `actualValue` to this factory.
     * The implementation should register the value to the list of `known values` of the parameter.
     *
     * @param actualValue An actual value to be added to this factory.
     * @param <F>         The type of the implementation of this interface
     * @return This object.
     */
    <F extends Descriptor<T>> F addActualValue(T actualValue);

    @SuppressWarnings("unchecked")
    default <F extends Descriptor<T>> F addActualValues(List<T> actualValues) {
      actualValues.forEach(this::addActualValue);
      return (F) this;
    }

    /**
     * Create the parameter.
     * This is a builder method of this class.
     *
     * @param name The name of the parameter with which the parameter is created.
     * @return The new parameter.
     */
    Parameter<T> create(String name);

    List<T> knownValues();

    /**
     * A base implementation class for {@code Parameter.Factory}.
     *
     * @param <T> The type of the parameter values.
     */
    abstract class Base<T> implements Descriptor<T> {
      protected final List<T> knownValues = new LinkedList<>();

      @SuppressWarnings("unchecked")
      @Override
      public <F extends Descriptor<T>> F addActualValue(T actualValue) {
        knownValues.add(actualValue);
        return (F) this;
      }

      @Override
      public List<T> knownValues() {
        return this.knownValues;
      }
    }
  }

  /**
   * A base class for parameters.
   *
   * @param <T> The type of the parameter values
   */
  abstract class Base<T> implements Parameter<T> {
    /**
     * The name of this parameter.
     */
    protected final String name;

    /**
     * Known values of this parameter.
     */
    private final Descriptor<T> descriptor;

    protected Base(String name, Parameter.Descriptor<T> descriptor) {
      this.name = requireNonNull(name);
      this.descriptor = requireNonNull(descriptor);
    }


    @Override
    public String getName() {
      return this.name;
    }

    @Override
    public FactorSpace toFactorSpace() {
      return FactorSpace.create(decompose(), generateConstraints());
    }

    @Override
    public List<T> getKnownValues() {
      return this.descriptor.knownValues();
    }

    @Override
    public Descriptor<T> descriptor() {
      return this.descriptor;
    }

    /**
     * Decomposes (or encodes) this parameter into factors, which covering array generation engines can handle.
     *
     * @return The list of factors.
     */
    protected abstract List<Factor> decompose();

    /**
     * Generates constraints over factors.
     *
     * @return The list of generated constraints.
     */
    protected abstract List<Constraint> generateConstraints();
  }
}