package au.id.zancanaro.javacheck; import java.util.Iterator; import java.util.List; import java.util.Random; import java.util.function.Function; /** * Generators are a way of producing random objects and their associated shrink * trees in a controlled and deterministic way. * * A generator must implement one method: {@link #generate(Random, int)}. The * {@link ShrinkTree} it produces defines both the value to be returned, as well * as the shrink tree for that value. * * Generators form a "monad", and hence have the {@link #map(Function)} and * {@link #flatMap(Function)} methods for composition. The helper methods {@link * #pure(Object)}, {@link #tuple(Generator[])} and {@link #list(int, Generator)} * allow for composition at a lower level than the monadic actions.. * * @param The type generated by this generator. */ @SuppressWarnings("unused") public interface Generator { /** * Return a {@link ShrinkTree} containing a new random value of the required * type, as well as its associated shrink tree. * * Generators also have an abstract notion of "size". Represented by an * integer, generators should use this as a guide for how large an object to * generate. There is no specific meaning for this value, so the precise * interpretation will depend on the specifics of the generator. * * @param random The random source for generation purposes * @param size An integer specifying how "big" a thing to produce * @return The {@link ShrinkTree} specifying the generated thing and its * shrink tree */ ShrinkTree generate(Random random, int size); /** * A generator which simply generates the provided value. Does not shrink. * * @param value The value to generate * @param The type of the generated value * @return A {@link Generator} which generates the provided value */ static Generator pure(T value) { return (random, size) -> ShrinkTree.pure(value); } /** * A generator which generates a {@link List}, with each element taken from * its corresponding generator. (That is: return[i] = * generators[i].generate(...).) * * Shrinking for this type involves attempting to shrink each subtree in * turn, recursively. * * @param generators The generators to use for each term in the generated * result * @param The parameter type of the generated {@link List} * @return A {@link Generator} returning a {@link List} */ @SafeVarargs static Generator> tuple(Generator... generators) { return (random, size) -> { @SuppressWarnings("unchecked") ShrinkTree[] result = (ShrinkTree[]) new ShrinkTree[generators.length]; int index = 0; for (Generator generator : generators) { result[index++] = generator.generate(random, size); } return ShrinkTree.zip(Function.identity(), result); }; } /** * A generator which generates a {@link List} of length count, with each * element taken from the provided generator. * * Shrinking for this type involves attempting to remove terms and shrink * each subtree in turn, recursively. (If the length must remain fixed then * the {@link ShrinkTree} produced by this generator should be filtered with * {@link ShrinkTree#filter(java.util.function.Predicate)}. * * @param count The length of the list to generate * @param generator The generator to use for each term in the generated * result * @param The parameter type of the generated {@link List} * @return A {@link Generator} returning a {@link List} */ static Generator> list(int count, Generator generator) { return (random, size) -> { @SuppressWarnings("unchecked") ShrinkTree[] result = (ShrinkTree[]) new ShrinkTree[count]; for (int i = 0; i < count; ++i) { result[i] = generator.generate(random, size); } return ShrinkTree.shrink(Function.identity(), result); }; } /** * Transform the result of a generator by passing it through a function. * * Maps all values in the shrink of this through f, as well. * * @param f The transformation function * @param The result of the transformation * @return A new generator resulting from mapping f over this */ default Generator map(Function f) { return (random, size) -> this.generate(random, size).map(f); } /** * Produce a new generator relying on the value generated by this generator * * Shrinking is a bit hard to predict under flatMap, as it will first * attempt to shrink this, resulting in the re-evaluation of action, and * hence the re-generation of the subtree. * * @param action A function to produce the new generator * @param The type of the returned generator * @return A new generator resulting from calling the provided action on the * result of this */ default Generator flatMap(Function> action) { return (random, size) -> ShrinkTree.join(this.generate(random, size).map(action).map(g -> g.generate(random, size))); } default Iterator sample(Random random, int maxSize) { return new GeneratorSampleIterator<>(this, random, maxSize); } }