Negative.java

package com.github.dakusui.jcunitx.pipeline.stages.generators;

import com.github.dakusui.combinatoradix.Cartesianator;
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 com.github.dakusui.jcunitx.pipeline.Requirement;
import com.github.dakusui.jcunitx.pipeline.stages.Generator;

import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;

import static java.util.stream.Collectors.toList;

public class Negative extends Generator.Base {
  private final List<AArray> regularTestCases;
  private final List<AArray> seeds;

  public Negative(List<AArray> regularTestCases, List<AArray> seeds, FactorSpace factorSpace, Requirement requirement) {
    super(factorSpace, requirement);
    this.regularTestCases = regularTestCases;
    this.seeds = seeds;
  }

  @Override
  public List<AArray> generateCore() {
    return generateNegativeTests(this.regularTestCases, factorSpace, seeds);
  }

  private static List<AArray> generateNegativeTests(List<AArray> tuples, FactorSpace factorSpace, List<AArray> seeds) {
    return new LinkedList<AArray>() {{
      //noinspection SimplifiableConditionalExpression
      factorSpace.getConstraints(
      ).stream(
      ).filter(
          (Constraint each) -> seeds.stream(
          ).filter(
              ////
              //   If there exists any seed which violates one and only one constraint,
              // a (negative) test case to violate the constraint doesn't need to be generated.
              //   Such a constraint is filtered out here.
              (AArray seed) -> factorSpace.getConstraints(
              ).stream(
              ).allMatch(
                  (Constraint constraint) ->
                      each == constraint ?
                          !constraint.test(seed) : // Violates each constraint
                          constraint.test(seed)    // But not violate any others
              )
          ).collect(toList()).size() != 1
      ).forEach(
          (Constraint each) ->
              createNegativeTestForConstraint(
                  each,
                  exclude(
                      each,
                      factorSpace.getConstraints()
                  ),
                  composeFactorMap(factorSpace),
                  tuples
              ).ifPresent(
                  this::add
              )
      );
    }};
  }

  private static List<Constraint> exclude(Constraint target, List<Constraint> all) {
    return new LinkedList<Constraint>(all) {{
      remove(target);
    }};
  }

  private static Map<String, List<Object>> composeFactorMap(FactorSpace factorSpace) {
    return new LinkedHashMap<String, List<Object>>() {{
      factorSpace.getFactors().forEach((Factor each) -> put(each.getName(), each.getLevels()));
    }};
  }

  private static Optional<AArray> createNegativeTestForConstraint(Constraint target, List<Constraint> rest, Map<String, List<Object>> parameters, List<AArray> tuples) {
    long leastCollateralConstraints = rest.size();
    Optional<AArray> ret = Optional.empty();
    OUTER:
    for (AArray base : tuples) {
      for (List<Object> each : createCartesianator(target, parameters)) {
        AArray modified = modifyTupleWithValues(base, composeValues(target.involvedKeys(), each));
        if (target.test(modified))
          continue;
        long numCollaterals = rest.stream().filter(constraint -> !constraint.test(modified)).count();
        if (numCollaterals != 0) {
          if (numCollaterals < leastCollateralConstraints) {
            leastCollateralConstraints = numCollaterals;
            ret = Optional.of(modified);
          }
          continue;
        }
        ret = Optional.of(modified);
        break OUTER;
      }
    }
    return ret;
  }

  private static AArray composeValues(List<String> involvedKeys, List<Object> values) {
    return new AArray.Impl() {{
      AtomicInteger i = new AtomicInteger(0);
      involvedKeys.forEach((String eachKey) -> put(eachKey, values.get(i.getAndIncrement())));
    }};
  }

  private static AArray modifyTupleWithValues(AArray in, AArray values) {
    return new AArray.Builder()
        .putAll(in)
        .putAll(new HashMap<String, Object>() {{
          values.keySet().forEach(eachKey -> put(eachKey, values.get(eachKey)));
        }})
        .build();
  }

  private static Cartesianator<Object> createCartesianator(final Constraint target, final Map<String, List<Object>> parameters) {
    return new Cartesianator<Object>(target.involvedKeys().stream().map(parameters::get).collect(toList())) {
    };
  }

}