package au.id.zancanaro.javacheck; 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 RoseTree} 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. */ public interface Generator { /** * Return a {@link RoseTree} 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 RoseTree} specifying the generated thing and its * shrink tree */ RoseTree 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) -> RoseTree.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") RoseTree[] result = (RoseTree[]) new RoseTree[generators.length]; int index = 0; for (Generator generator : generators) { result[index++] = generator.generate(random, size); } return RoseTree.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 RoseTree} produced by this generator should be filtered with * {@link RoseTree#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") RoseTree[] result = (RoseTree[]) new RoseTree[count]; for (int i = 0; i < count; ++i) { result[i] = generator.generate(random, size); } return RoseTree.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 {@link #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) -> RoseTree.join(this.generate(random, size).map(action).map(g -> g.generate(random, size))); } }