/* __ __ __ __ __ ___ * \ \ / / \ \ / / __/ * \ \/ / /\ \ \/ / / * \____/__/ \__\____/__/.ɪᴏ * ᶜᵒᵖʸʳᶦᵍʰᵗ ᵇʸ ᵛᵃᵛʳ ⁻ ˡᶦᶜᵉⁿˢᵉᵈ ᵘⁿᵈᵉʳ ᵗʰᵉ ᵃᵖᵃᶜʰᵉ ˡᶦᶜᵉⁿˢᵉ ᵛᵉʳˢᶦᵒⁿ ᵗʷᵒ ᵈᵒᵗ ᶻᵉʳᵒ */ package io.vavr.test; import io.vavr.Tuple2; import io.vavr.collection.Iterator; import io.vavr.collection.List; import io.vavr.collection.Stream; import io.vavr.collection.Vector; import java.util.Objects; import java.util.Random; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Predicate; /** * Generators are the building blocks for providing arbitrary objects. * <p> * To ease the creation of Arbitraries, Gen is a FunctionalInterface which extends {@code Function<Random, T>}. * <p> * Gen objects are obtained via one of the methods {@code choose}, {@code fail}, {@code frequency}, {@code of} and * {@code oneOf}. * <p> * Given Gen objects may be transformed using one of the methods {@code filter}, {@code map} and {@code flatMap}. * <p> * A simple way to obtain an Arbitrary of a Gen is to call {@linkplain Gen#arbitrary()}. * This will ignore the size hint of Arbitrary. * * @param <T> type of generated objects * @author Daniel Dietrich * @see Arbitrary */ @FunctionalInterface public interface Gen<T> { /** * Functional interface of this generator. * * @param random a random number generator * @return A generated value of type T. */ T apply(Random random); /** * A generator which constantly returns t. * * @param t A value. * @param <T> Type of t. * @return A new T generator */ static <T> Gen<T> of(T t) { return ignored -> t; } static <T> Gen<T> of(T seed, Function<? super T, ? extends T> next) { Objects.requireNonNull(next, "next is null"); final Iterator<T> iterator = Iterator.iterate(seed, next); return ignored -> iterator.next(); } /** * Chooses an int between min and max, bounds inclusive and numbers distributed according to the distribution of * the underlying random number generator. * <p> * Note: min and max are internally swapped if min > max. * * @param min lower bound * @param max upper bound * @return A new int generator */ static Gen<Integer> choose(int min, int max) { if (min == max) { return ignored -> min; } else { final int _min = Math.min(min, max); final int _max = Math.max(min, max); return rng -> rng.nextInt(Math.abs(_max - _min) + 1) + _min; } } /** * Chooses a long between min and max, bounds inclusive and numbers distributed according to the distribution of * the underlying random number generator. * <p> * Note: min and max are internally swapped if min > max. * * @param min lower bound * @param max upper bound * @return A new long generator */ static Gen<Long> choose(long min, long max) { if (min == max) { return ignored -> min; } else { return random -> { final double d = random.nextDouble(); final long _min = Math.min(min, max); final long _max = Math.max(min, max); return (long) ((d * _max) + ((1.0 - d) * _min) + d); }; } } /** * Chooses a double between min and max, bounds inclusive and numbers distributed according to the distribution * of the underlying random number generator. * <p> * Note: min and max are internally swapped if min > max. * * @param min lower bound * @param max upper bound * @return A new double generator * @throws IllegalArgumentException if min or max is infinite, min or max is not a number (NaN) */ static Gen<Double> choose(double min, double max) { if (Double.isInfinite(min)) { throw new IllegalArgumentException("min is infinite"); } if (Double.isInfinite(max)) { throw new IllegalArgumentException("max is infinite"); } if (Double.isNaN(min)) { throw new IllegalArgumentException("min is not a number (NaN)"); } if (Double.isNaN(max)) { throw new IllegalArgumentException("max is not a number (NaN)"); } if (min == max) { return ignored -> min; } else { return random -> { final double d = random.nextDouble(); final double _min = Math.min(min, max); final double _max = Math.max(min, max); return d * _max + (1.0 - d) * _min; }; } } /** * Chooses a char between min and max, bounds inclusive and chars distributed according to the underlying random * number generator. * <p> * Note: min and max are internally swapped if min > max. * * @param min lower bound * @param max upper bound * @return A new char generator */ static Gen<Character> choose(char min, char max) { if (min == max) { return ignored -> min; } else { return Gen.choose((int) min, (int) max).map(i -> (char) (int) i); } } /** * Chooses a char from all chars in the array * * @param characters array with the characters to choose from * @return A new array generator */ static Gen<Character> choose(char... characters) { Objects.requireNonNull(characters, "characters is null"); final Character[] validCharacters = List.ofAll(characters).toJavaArray(Character.class); return choose(validCharacters); } /** * Chooses an enum value from all the enum constants defined in the enumerated type. * * @param clazz Enum class * @param <T> type of enum constants * @return A new enum generator */ static <T extends Enum<T>> Gen<T> choose(Class<T> clazz) { Objects.requireNonNull(clazz, "clazz is null"); return Gen.choose(clazz.getEnumConstants()); } /** * Chooses a value from all values in the array. * * @param values array with the values to choose from * @param <T> value type * @return A new array generator */ @SafeVarargs @SuppressWarnings("varargs") static <T> Gen<T> choose(T... values) { Objects.requireNonNull(values, "values is null"); if (values.length == 0) { return Gen.fail("Empty array"); } else { return Gen.choose(0, values.length - 1).map(i -> values[i]); } } /** * Chooses a value from all values in the iterable * * @param values iterable with the values to choose from. * @param <T> value type * @return A new iterable generator */ static <T> Gen<T> choose(Iterable<T> values) { Objects.requireNonNull(values, "values is null"); final Iterator<T> iterator = Iterator.ofAll(values); if (!iterator.hasNext()) { throw new IllegalArgumentException("Empty iterable"); } @SuppressWarnings("unchecked") final T[] array = (T[]) iterator.toJavaArray(); return choose(array); } /** * A failing generator which throws a RuntimeException("failed"). * * @param <T> Type of values theoretically generated. * @return A new generator which always fails with the message "failed" */ static <T> Gen<T> fail() { return fail("failed"); } /** * A failing generator which throws a RuntimeException. * * @param message Message thrown. * @param <T> Type of values theoretically generated. * @return A new generator which always fails with the given message */ static <T> Gen<T> fail(String message) { return ignored -> { throw new RuntimeException(message); }; } /** * Chooses one of the given generators according to their frequency. * Only generators with positive frequencies are used in returned * generator. * * @param generators A non-empty array of Tuples (frequency, generator) * @param <T> Type to be generated * @return A new T generator * @throws java.lang.NullPointerException if generators is null * @throws java.lang.IllegalArgumentException if generators doesn't contain any generator with positive frequency */ @SuppressWarnings("varargs") @SafeVarargs static <T> Gen<T> frequency(Tuple2<Integer, Gen<T>>... generators) { Objects.requireNonNull(generators, "generators is null"); if (generators.length == 0) { throw new IllegalArgumentException("generators is empty"); } final Iterable<Tuple2<Integer, Gen<T>>> iterable = Stream.of(generators); return frequency(iterable); } /** * Chooses one of the given generators according to their frequency. * Only generators with positive frequencies ares used in returned * generator. * * @param generators A non-empty traversable of Tuples (frequency, generator) * @param <T> Type to be generated * @return A new T generator * @throws java.lang.NullPointerException if generators is null * @throws java.lang.IllegalArgumentException if generators doesn't contain any generator with positive frequency */ static <T> Gen<T> frequency(Iterable<Tuple2<Integer, Gen<T>>> generators) { Objects.requireNonNull(generators, "generators is null"); final Vector<Tuple2<Integer, Gen<T>>> filtered = Iterator.ofAll(generators) .filter(t -> t._1() > 0).toVector(); if (filtered.isEmpty()) { throw new IllegalArgumentException("no generator with positive weight"); } final int size = filtered.map(t -> t._1).sum().intValue(); return choose(1, size).flatMap(n -> GenModule.frequency(n, filtered.iterator())); } /** * Intersperse values from this generator instance with those of another. * * @param other another T generator to accept values from. * @return A new T generator */ default Gen<T> intersperse(Gen<T> other) { final Iterator<Gen<T>> iter = Iterator.continually(this).intersperse(other); return random -> iter.get().apply(random); } /** * Randomly chooses one of the given generators. * * @param generators A non-empty array of generators * @param <T> Type to be generated * @return A new T generator * @throws java.lang.NullPointerException if generators is null * @throws java.lang.IllegalArgumentException if generators is empty */ @SafeVarargs static <T> Gen<T> oneOf(Gen<T>... generators) { Objects.requireNonNull(generators, "generators is null"); if (generators.length == 0) { throw new IllegalArgumentException("generators is empty"); } return choose(0, generators.length - 1).flatMap(i -> generators[i]); } /** * Randomly chooses one of the given generators. * * @param generators A non-empty Iterable of generators * @param <T> Type to be generated * @return A new T generator * @throws java.lang.NullPointerException if generators is null * @throws java.lang.IllegalArgumentException if generators is empty */ static <T> Gen<T> oneOf(Iterable<Gen<T>> generators) { Objects.requireNonNull(generators, "generators is null"); final Stream<Gen<T>> stream = Stream.ofAll(generators); if (stream.isEmpty()) { throw new IllegalArgumentException("generators is empty"); } @SuppressWarnings("unchecked") final Gen<T>[] array = stream.toJavaArray((Class<Gen<T>>) (Object) Gen.class); return oneOf(array); } /** * Converts this Gen to an Arbitrary * * @return An arbitrary which returns this generator regardless of the provided size hint n */ default Arbitrary<T> arbitrary() { return n -> this; } /** * Returns a generator based on this generator which produces values that fulfill the given predicate. * * @param predicate A predicate * @return A new generator */ default Gen<T> filter(Predicate<? super T> predicate) { Objects.requireNonNull(predicate, "predicate is null"); return random -> { int count = 0; final int filterThreshold = Integer.MAX_VALUE; T t; while (!predicate.test(t = apply(random))) { // it may take a looooooong time to hit this condition! if (++count == filterThreshold) { throw new IllegalStateException("empty filter"); } } return t; }; } /** * Maps generated Ts to Us. * * @param mapper A function that maps a generated T to a new generator which generates objects of type U. * @param <U> Type of generated objects of the new generator * @return A new generator */ default <U> Gen<U> flatMap(Function<? super T, ? extends Gen<? extends U>> mapper) { Objects.requireNonNull(mapper, "mapper is null"); return random -> mapper.apply(apply(random)).apply(random); } /** * Maps generated Ts to Us. * * @param mapper A function that maps a generated T to an object of type U. * @param <U> Type of the mapped object * @return A new generator */ default <U> Gen<U> map(Function<? super T, ? extends U> mapper) { Objects.requireNonNull(mapper, "mapper is null"); return random -> mapper.apply(apply(random)); } default Gen<T> peek(Consumer<? super T> action) { return random -> { final T t = apply(random); action.accept(t); return t; }; } /** * Transforms this {@code Gen}. * * @param f A transformation * @param <U> Type of transformation result * @return An instance of type {@code U} * @throws NullPointerException if {@code f} is null */ default <U> U transform(Function<? super Gen<T>, ? extends U> f) { Objects.requireNonNull(f, "f is null"); return f.apply(this); } } interface GenModule { /** * Chooses a Gen according to the given frequencies. * * @param n a random value between 0 and sum(frequencies) - 1 * @param iter a non-empty Iterator of (frequency, Gen) pairs * @param <T> type of generated values * @return A value generator, choosen according to the given frequencies and the underlying n */ static <T> Gen<T> frequency(int n, java.util.Iterator<Tuple2<Integer, Gen<T>>> iter) { do { final Tuple2<Integer, Gen<T>> freqGen = iter.next(); final int k = freqGen._1; if (n <= k) { return freqGen._2; } else { n = n - k; } } while (true); } }