/* __ __ __ __ __ ___
* \ \ / / \ \ / / __/
* \ \/ / /\ \ \/ / /
* \____/__/ \__\____/__/.ɪᴏ
* ᶜᵒᵖʸʳᶦᵍʰᵗ ᵇʸ ᵛᵃᵛʳ ⁻ ˡᶦᶜᵉⁿˢᵉᵈ ᵘⁿᵈᵉʳ ᵗʰᵉ ᵃᵖᵃᶜʰᵉ ˡᶦᶜᵉⁿˢᵉ ᵛᵉʳˢᶦᵒⁿ ᵗʷᵒ ᵈᵒᵗ ᶻᵉʳᵒ
*/
package io.vavr.test;
import io.vavr.collection.List;
import io.vavr.collection.Stream;
import java.time.Duration;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.Comparator;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
/**
* Represents an arbitrary object of type T.
*
* @param <T> The type of the arbitrary object.
* @author Daniel Dietrich
*/
@FunctionalInterface
public interface Arbitrary<T> {
/**
* Returns a generator for objects of type T.
* Use {@link Gen#map(Function)} and {@link Gen#flatMap(Function)} to
* combine object generators.
* <p>
* Example:
* <pre>
* <code>
* // represents arbitrary binary trees of a certain depth n
* final class ArbitraryTree implements Arbitrary<BinaryTree<Integer>> {
* @Override
* public Gen<BinaryTree<Integer>> apply(int n) {
* return Gen.choose(-1000, 1000).flatMap(value -> {
* if (n == 0) {
* return Gen.of(BinaryTree.leaf(value));
* } else {
* return Gen.frequency(
* Tuple.of(1, Gen.of(BinaryTree.leaf(value))),
* Tuple.of(4, Gen.of(BinaryTree.branch(apply(n / 2).get(), value, apply(n / 2).get())))
* );
* }
* });
* }
* }
*
* // tree generator with a size hint of 10
* final Gen<BinaryTree<Integer>> treeGen = new ArbitraryTree().apply(10);
*
* // stream sum of tree node values to console for 100 arbitrary trees
* Stream.of(() -> treeGen.apply(RNG.get())).map(Tree::sum).take(100).stdout();
* </code>
* </pre>
*
* @param size A (not necessarily positive) size parameter which may be interpreted individually and is constant for all arbitrary objects regarding one property check.
* @return A generator for objects of type T.
*/
Gen<T> apply(int size);
/**
* Returns an Arbitrary based on this Arbitrary which produces unique values.
*
* @return A new generator
*/
default Arbitrary<T> distinct() {
return distinctBy(Function.identity());
}
/**
* Returns an Arbitrary based on this Arbitrary which produces unique values based on the given comparator.
*
* @param comparator A comparator
* @return A new generator
*/
default Arbitrary<T> distinctBy(Comparator<? super T> comparator) {
Objects.requireNonNull(comparator, "comparator is null");
final java.util.Set<T> seen = new java.util.TreeSet<>(comparator);
return filter(seen::add);
}
/**
* Returns an Arbitrary based on this Arbitrary which produces unique values based on the given function.
*
* @param <U> key type
* @param keyExtractor A function
* @return A new generator
*/
default <U> Arbitrary<T> distinctBy(Function<? super T, ? extends U> keyExtractor) {
Objects.requireNonNull(keyExtractor, "keyExtractor is null");
final java.util.Set<U> seen = new java.util.HashSet<>();
return filter(t -> seen.add(keyExtractor.apply(t)));
}
/**
* Returns an Arbitrary based on this Arbitrary which produces values that fulfill the given predicate.
*
* @param predicate A predicate
* @return A new generator
*/
default Arbitrary<T> filter(Predicate<? super T> predicate) {
return size -> apply(size).filter(predicate);
}
/**
* Maps arbitrary objects T to arbitrary object U.
*
* @param mapper A function that maps arbitrary Ts to arbitrary Us given a mapper.
* @param <U> New type of arbitrary objects
* @return A new Arbitrary
*/
default <U> Arbitrary<U> flatMap(Function<? super T, ? extends Arbitrary<? extends U>> mapper) {
return size -> {
final Gen<T> gen = apply(size);
return random -> mapper.apply(gen.apply(random)).apply(size).apply(random);
};
}
/**
* Intersperses values from this arbitrary instance with those of another.
*
* @param other another T arbitrary to accept values from.
* @return A new T arbitrary
*/
default Arbitrary<T> intersperse(Arbitrary<T> other) {
Objects.requireNonNull(other, "other is null");
return size -> this.apply(size).intersperse(other.apply(size));
}
/**
* Maps arbitrary objects T to arbitrary object U.
*
* @param mapper A function that maps an arbitrary T to an object of type U.
* @param <U> Type of the mapped object
* @return A new generator
*/
default <U> Arbitrary<U> map(Function<? super T, ? extends U> mapper) {
Objects.requireNonNull(mapper, "mapper is null");
return n -> {
final Gen<T> generator = apply(n);
return random -> mapper.apply(generator.apply(random));
};
}
default Arbitrary<T> peek(Consumer<? super T> action) {
return size -> apply(size).peek(action);
}
/**
* Transforms this {@code Arbitrary}.
*
* @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 Arbitrary<T>, ? extends U> f) {
Objects.requireNonNull(f, "f is null");
return f.apply(this);
}
/**
* Generates an arbitrary value from a fixed set of values
*
* @param values A fixed set of values
* @param <U> Type of generator value
* @return A new generator
*/
@SafeVarargs
@SuppressWarnings("varargs")
static <U> Arbitrary<U> of(U... values) {
return ofAll(Gen.choose(values));
}
/**
* Generates an arbitrary value from a given generator
*
* @param generator A generator to produce arbitrary values
* @param <U> Type of generator value
* @return A new generator
*/
static <U> Arbitrary<U> ofAll(Gen<U> generator) {
return size -> generator;
}
/**
* Generates arbitrary integer values.
*
* @return A new Arbitrary of Integer
*/
static Arbitrary<Integer> integer() {
return size -> Gen.choose(-size, size);
}
/**
* Generates arbitrary {@link LocalDateTime}s with {@link LocalDateTime#now()} as {@code median} and
* {@link ChronoUnit#DAYS} as chronological unit.
*
* @return A new Arbitrary of LocalDateTime
* @see #localDateTime(LocalDateTime, ChronoUnit)
*/
static Arbitrary<LocalDateTime> localDateTime() {
return localDateTime(ChronoUnit.DAYS);
}
/**
* Generates arbitrary {@link LocalDateTime}s with {@link LocalDateTime#now()} as {@code median}.
*
* @param unit Chronological unit of {@code size}
* @return A new Arbitrary of LocalDateTime
* @see #localDateTime(LocalDateTime, ChronoUnit)
*/
static Arbitrary<LocalDateTime> localDateTime(ChronoUnit unit) {
return localDateTime(LocalDateTime.now(), unit);
}
/**
* Generates arbitrary {@link LocalDateTime}s. All generated values are drawn from a range with {@code median}
* as center and {@code median +/- size} as included boundaries. {@code unit} defines the chronological unit
* of {@code size}.
*
* <p>
* Example:
* <pre>
* <code>
* Arbitrary.localDateTime(LocalDateTime.now(), ChronoUnit.YEARS);
* </code>
* </pre>
*
* @param median Center of the LocalDateTime range
* @param unit Chronological unit of {@code size}
* @return A new Arbitrary of LocalDateTime
*/
static Arbitrary<LocalDateTime> localDateTime(LocalDateTime median, ChronoUnit unit) {
Objects.requireNonNull(median, "median is null");
Objects.requireNonNull(unit, "unit is null");
return size -> {
if(size == 0) {
return Gen.of(median);
}
final LocalDateTime start = median.minus(size, unit);
final LocalDateTime end = median.plus(size, unit);
final long duration = Duration.between(start, end).toMillis();
final Gen<Long> from = Gen.choose(0, duration);
return random -> start.plus(from.apply(random), ChronoUnit.MILLIS);
};
}
/**
* Generates arbitrary strings based on a given alphabet represented by <em>gen</em>.
* <p>
* Example:
* <pre>
* <code>
* Arbitrary.string(
* Gen.frequency(
* Tuple.of(1, Gen.choose('A', 'Z')),
* Tuple.of(1, Gen.choose('a', 'z')),
* Tuple.of(1, Gen.choose('0', '9'))));
* </code>
* </pre>
*
* @param gen A character generator
* @return a new Arbitrary of String
*/
static Arbitrary<String> string(Gen<Character> gen) {
return size -> random -> Gen.choose(0, size).map(i -> {
final char[] chars = new char[i];
for (int j = 0; j < i; j++) {
chars[j] = gen.apply(random);
}
return new String(chars);
}).apply(random);
}
/**
* Generates arbitrary lists based on a given element generator arbitraryT.
* <p>
* Example:
* <pre>
* <code>
* Arbitrary.list(Arbitrary.integer());
* </code>
* </pre>
*
* @param arbitraryT Arbitrary elements of type T
* @param <T> Component type of the List
* @return a new Arbitrary of List<T>
*/
static <T> Arbitrary<List<T>> list(Arbitrary<T> arbitraryT) {
return size -> {
final Gen<T> genT = arbitraryT.apply(size);
return random -> List.fill(Gen.choose(0, size).apply(random), () -> genT.apply(random));
};
}
/**
* Generates arbitrary streams based on a given element generator arbitraryT.
* <p>
* Example:
* <pre>
* <code>
* Arbitrary.stream(Arbitrary.integer());
* </code>
* </pre>
*
* @param arbitraryT Arbitrary elements of type T
* @param <T> Component type of the Stream
* @return a new Arbitrary of Stream<T>
*/
static <T> Arbitrary<Stream<T>> stream(Arbitrary<T> arbitraryT) {
return size -> {
final Gen<T> genT = arbitraryT.apply(size);
return random -> Stream.continually(() -> genT.apply(random))
.take(Gen.choose(0, size).apply(random));
};
}
}