CharClassesGen.java

/*
 * Copyright (C) 1998-2019  Gerwin Klein <lsf@jflex.de>
 * SPDX-License-Identifier: BSD-3-Clause
 */

package jflex.core.unicode;

import com.pholser.junit.quickcheck.generator.GenerationStatus;
import com.pholser.junit.quickcheck.generator.Generator;
import com.pholser.junit.quickcheck.generator.InRange;
import com.pholser.junit.quickcheck.generator.Size;
import com.pholser.junit.quickcheck.random.SourceOfRandomness;
import java.util.ArrayList;
import java.util.List;

/**
 * Generator for random {@link CharClasses} instances.
 *
 * @author Gerwin Klein
 * @version JFlex 1.10.0-SNAPSHOT
 * @see CharClasses
 */
public class CharClassesGen extends Generator<CharClasses> {

  /**
   * Min bound for number of classes to add to default (must be => 0).
   *
   * <p>Number of classes in the final partition is added classes + 1.
   */
  private int minSize = 0;
  /** Max bound for number of classes */
  private int maxSize = 5;

  /** Generator for classes (= IntCharSet) */
  private final IntCharSetGen classGen;

  /** The UnicodeProperties that determine maxCharCode and getCaseless */
  // TODO(lsf): make UnicodeProperties a configurable option, return something useful
  private UnicodeProperties unicodeProps;

  /** Constructs generator for CharClasses */
  public CharClassesGen() {
    super(CharClasses.class);
    this.classGen = new IntCharSetGen();
  }

  /**
   * Constructs smallest possible test value, in this case the char class with only one partition.
   *
   * @return a CharClasses object with one partition.
   */
  private CharClasses smallest() {
    // TODO(lsf): make maxChar a configurable property
    return new CharClasses(CharClasses.maxChar, new UnicodePropertiesSupplier());
  }

  @Override
  public CharClasses generate(SourceOfRandomness r, GenerationStatus status) {
    CharClasses result = smallest();

    int numClasses = r.nextInt(minSize, maxSize);
    for (int i = 0; i < numClasses; i++) {
      // TOOD(lsf): add caseless (needs actual UnicodeProperties)
      result.makeClass(classGen.generate(r, status), false);
    }

    return result;
  }

  /**
   * Configure this generator to only produce classes in the given range.
   *
   * @param range annotation that contains the char class constraints
   */
  public void configure(InRange range) {
    classGen.configure(range);
  }

  /**
   * Configure this generator to only produce CharClasses with a given range of number of classes.
   *
   * @param size annotation that contains how many classes should be added to the defualt
   *     (one-class) CharClassSet,
   */
  public void configure(Size size) {
    minSize = Math.max(0, size.min());
    maxSize = size.max();
  }

  /** For initialising the CharClasses object. */
  public class UnicodePropertiesSupplier implements ILexScan {
    @Override
    public UnicodeProperties getUnicodeProperties() {
      return unicodeProps;
    }
  }

  @Override
  public List<CharClasses> doShrink(SourceOfRandomness random, CharClasses larger) {
    List<CharClasses> results = new ArrayList<>();

    if (larger.getNumClasses() == 1) {
      // cannot shrink further
      return results;
    }

    // Try smallest possible value
    results.add(smallest());

    // try merging partitions into size 2
    List<IntCharSet> classes = larger.allClasses();
    for (int split = 1; split < classes.size() - 1; split++) {
      IntCharSet set = new IntCharSet();
      for (int i = 0; i < split; i++) {
        set.add(classes.get(i));
      }
      CharClasses next = smallest();
      next.makeClass(set, false);

      assert next.invariants() : next;
      results.add(next);
    }

    return results;
  }
}