net.jqwik.api.Builders Maven / Gradle / Ivy
package net.jqwik.api;
import java.util.*;
import java.util.function.*;
import java.util.stream.*;
import org.apiguardian.api.*;
import org.jspecify.annotations.*;
import net.jqwik.api.Tuple.*;
import net.jqwik.api.support.*;
import static org.apiguardian.api.API.Status.*;
@API(status = MAINTAINED, since = "1.5.4")
public class Builders {
private Builders() {
}
/**
* Combine Arbitraries by means of a builder.
*
* @param builderSupplier The supplier will be called freshly for each value generation.
* For exhaustive generation all supplied objects are
* supposed to be identical.
* @return BuilderCombinator instance
*/
public static BuilderCombinator withBuilder(Supplier builderSupplier) {
return new BuilderCombinator<>(builderSupplier, Collections.emptyList());
}
/**
* Provide access to combinators through builder functionality.
*
* A builder is created through {@linkplain #withBuilder(Supplier)}.
*
* @param The builder's type
*/
public static class BuilderCombinator {
private final Supplier starter;
private final List, BiFunction>> mutators;
private BuilderCombinator(Supplier starter, List, BiFunction>> mutators) {
this.starter = starter;
this.mutators = mutators;
}
/**
* Use an arbitrary of type {@code T} in this builder
*
* @param arbitrary
* @param
* @return new {@linkplain CombinableBuilder} instance
*/
public CombinableBuilder use(Arbitrary arbitrary) {
return new CombinableBuilder<>(this, 1.0, arbitrary);
}
/**
* Create the final arbitrary.
*
* @param buildFunction Function to map a builder to an object
* @param the target object's type
* @return arbitrary of target object
*/
public Arbitrary build(Function buildFunction) {
class Holder {
final @Nullable Object value;
Holder(@Nullable Object value) {
this.value = value;
}
}
// Doing it in a single combine instead of flatMapping over all arbitraries
// leads to better performance and forgoes some problems with stateful builders
List>> arbitraries =
mutators.stream()
.map(mutator -> {
double presenceProbability = mutator.get1();
Arbitrary nullable = mutator.get2()
.map(value -> new Holder(value)); // Java 8 does not allow Holder::new here
return nullable.optional(presenceProbability);
})
.collect(Collectors.toList());
Arbitrary aBuilder = Combinators.combine(arbitraries).as(values -> {
B builder = starter.get();
for (int i = 0; i < values.size(); i++) {
Optional optional = values.get(i);
// optional.ifPresent does not work b/c builder is reassigned
if (optional.isPresent()) {
Object value = optional.get().value;
BiFunction mutator = mutators.get(i).get3();
//noinspection ConstantConditions: value is allowed to be null
builder = mutator.apply(builder, value);
}
}
return builder;
});
return aBuilder.map(buildFunction);
}
/**
* Create the final arbitrary if it's the builder itself.
*
* @return arbitrary of builder
*/
public Arbitrary build() {
return build(Function.identity());
}
/**
* Equality matters to allow memoization of resulting arbitraries
*/
@SuppressWarnings("unchecked")
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
BuilderCombinator that = (BuilderCombinator) o;
if (!LambdaSupport.areEqual(starter, that.starter)) return false;
return mutatorsAreEqual(mutators, that.mutators);
}
private boolean mutatorsAreEqual(
List, BiFunction>> leftMutators,
List, BiFunction>> rightMutators
) {
if (leftMutators.size() != rightMutators.size()) {
return false;
}
for (int i = 0; i < leftMutators.size(); i++) {
Tuple3, BiFunction> left = leftMutators.get(i);
Tuple3, BiFunction> right = rightMutators.get(i);
if(!left.get1().equals(right.get1())) return false;
if(!left.get2().equals(right.get2())) return false;
if(!LambdaSupport.areEqual(left.get3(), right.get3())) return false;
}
return true;
}
@Override
public int hashCode() {
return hashMutators();
}
private int hashMutators() {
// BiFunctions are not hashable, so we need to hash the mutators separately
return mutators.stream().map(Tuple2::get2).collect(Collectors.toList()).hashCode();
}
BuilderCombinator withMutator(double probabilityOfUse, Arbitrary