package au.id.zancanaro.javacheck; import au.id.zancanaro.javacheck.object.GeneratorProvider; import au.id.zancanaro.javacheck.object.ObjectGenerator; import java.util.*; import java.util.function.Consumer; import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; import java.util.stream.StreamSupport; @SuppressWarnings({"unused", "WeakerAccess"}) public final class Generators { private Generators() { } /** * Create a generator which explicitly depends on the current "size" * parameter. * * @param makeGenerator A function from size to a generator * @param The static type of the returned generator * @return The generator returned by makeGenerator */ public static Generator sized(Function> makeGenerator) { return (random, size) -> makeGenerator.apply(size).generate(random, size); } /** * Remove a generator's shrink tree. * * @param gen The generator * @param The generator's static type * @return A new generator which is the previous generator with shrink tree * removed */ public static Generator noShrink(Generator gen) { return gen.withShrinkStrategy(value -> Stream.empty()); } @SafeVarargs public static Generator oneOf(Generator... gens) { return integer(0, gens.length).flatMap(index -> gens[index].map(x -> (T) x)); } @SafeVarargs public static Generator elements(T... elements) { return elements(Arrays.asList(elements)); } public static Generator elements(List elements) { return integer(0, elements.size()).map(elements::get); } public static Generator bool() { return (random, size) -> ShrinkTree.pure(random.nextBoolean()) .withShrinkStrategy(boolShrinkStrategy()); } private static ShrinkStrategy boolShrinkStrategy() { return value -> (value ? Stream.of(false) : Stream.empty()); } public static Generator longInteger(long lower, long upper) { return (random, size) -> { long value = random.longs(lower, upper).findFirst().getAsLong(); long bound = lower > 0 ? lower : (upper < 0 ? upper : 0); return ShrinkTree.pure(value) .withShrinkStrategy(longShrinkStrategy(bound)); }; } public static Generator longInteger() { return sized(size -> longInteger(-size, size)); } private static ShrinkStrategy longShrinkStrategy(final long bound) { return value -> StreamSupport.stream(new Spliterators.AbstractSpliterator(Long.MAX_VALUE, Spliterator.ORDERED) { long curr = value - bound; @Override public boolean tryAdvance(Consumer action) { if (curr == 0) { return false; } else { action.accept(value - curr); curr /= 2; return true; } } }, false); } public static Generator integer(int lower, int upper) { return (random, size) -> { int value = random.ints(lower, upper).findFirst().getAsInt(); int bound = lower > 0 ? lower : (upper < 0 ? upper : 0); return ShrinkTree.pure(value) .withShrinkStrategy(intShrinkStrategy(bound)); }; } public static Generator integer() { return sized(size -> integer(-size, size)); } public static Generator natural() { return sized(size -> integer(0, size)); } private static ShrinkStrategy intShrinkStrategy(final int bound) { return value -> StreamSupport.stream(new Spliterators.AbstractSpliterator(Long.MAX_VALUE, Spliterator.ORDERED) { int curr = value - bound; @Override public boolean tryAdvance(Consumer action) { if (curr == 0) { return false; } else { action.accept(value - curr); curr /= 2; return true; } } }, false); } public static Generator doublePrecision(double lower, double upper) { return (random, size) -> { double value = random.doubles(lower, upper).findFirst().getAsDouble(); double bound = lower > 0 ? lower : (upper < 0 ? upper : 0); return ShrinkTree.pure(value) .withShrinkStrategy(doubleShrinkStrategy(bound, Double.MIN_NORMAL /* maybe pick a bigger epsilon? */)); }; } public static Generator doublePrecision() { return sized(size -> doublePrecision(-size, size)); } private static ShrinkStrategy doubleShrinkStrategy(final double bound, double epsilon) { return value -> StreamSupport.stream(new Spliterators.AbstractSpliterator(Long.MAX_VALUE, Spliterator.ORDERED) { double curr = value - bound; @Override public boolean tryAdvance(Consumer action) { if (Math.abs(curr) < epsilon) { return false; } else { action.accept(value - curr); curr /= 2; return true; } } }, false); } public static Generator> listOf(Generator gen, int minElements, int maxElements) { return (random, size) -> { Generator countGen = sized(s -> integer(minElements, maxElements)); int count = countGen.generate(random, size).getValue(); return Generator.list(count, gen) .generate(random, size) .filter(list -> minElements <= list.size() && list.size() < maxElements) .map(Collections::unmodifiableList); }; } public static Generator> listOf(Generator gen) { return (random, size) -> { Generator countGen = sized(s -> integer(0, s)); int count = countGen.generate(random, size).getValue(); return Generator.list(count, gen) .generate(random, size) .map(Collections::unmodifiableList); }; } @SuppressWarnings("unchecked") public static Generator> mapOf(Generator keyGen, Generator valueGen) { return (random, size) -> { Generator countGen = sized(s -> integer(0, s)); int count = countGen.generate(random, size).getValue(); return Generator.list(count, Generator.tuple(keyGen, valueGen)) .generate(random, size) .map(pairs -> pairs.stream() .collect(Collectors.toMap( pair -> (K) pair.get(0), pair -> (V) pair.get(1), (first, second) -> second))) .map(Collections::unmodifiableMap); }; } public static Generator character() { return integer(0, 256).map(i -> (char) i.intValue()); } public static Generator asciiCharacter() { return integer(32, 127).map(i -> (char) i.intValue()); } public static Generator alphaNumericCharacter() { return oneOf( integer(48, 58), integer(65, 91), integer(97, 123)).map(i -> (char) i.intValue()); } public static Generator alphaCharacter() { return oneOf( integer(65, 91), integer(97, 123)).map(i -> (char) i.intValue()); } private static String makeString(Character[] arr) { StringBuilder builder = new StringBuilder(arr.length); for (Character c : arr) { builder.append(c); } return builder.toString(); } public static Generator string() { return stringOf(character()); } public static Generator stringOf(Generator charGen) { return listOf(charGen).map(list -> { char[] chars = new char[list.size()]; int i = 0; for (Character c : list) { chars[i++] = c; } return String.valueOf(chars); }); } public static Generator ofType(Class type) { return new ObjectGenerator<>(type); } public static Generator ofType(Class type, GeneratorProvider provider) { return new ObjectGenerator<>(type, provider); } }