net.jqwik.engine.properties.arbitraries.RecursiveArbitrary Maven / Gradle / Ivy
package net.jqwik.engine.properties.arbitraries;
import java.util.*;
import java.util.function.*;
import net.jqwik.api.*;
import net.jqwik.api.support.*;
import org.jspecify.annotations.*;
public class RecursiveArbitrary implements Arbitrary {
private final Supplier extends Arbitrary> base;
private final Function super Arbitrary, ? extends Arbitrary> recur;
private final int depth;
// Not used for exhaustive generation
private final Arbitrary arbitrary;
private boolean isGeneratorMemoizable = true;
public RecursiveArbitrary(Supplier extends Arbitrary> base, Function super Arbitrary, ? extends Arbitrary> recur, int depth) {
this.base = base;
this.recur = recur;
this.depth = depth;
this.arbitrary = iteratedArbitrary();
}
@Override
public RandomGenerator generator(int genSize) {
return arbitrary.generator(genSize);
}
@Override
public EdgeCases edgeCases(int maxEdgeCases) {
// Very deep nesting tends to overflow the stack
if (depth > 100) {
return EdgeCases.none();
}
return arbitrary.edgeCases(maxEdgeCases);
}
@Override
public boolean isGeneratorMemoizable() {
return isGeneratorMemoizable;
}
@Override
public Optional> exhaustive(long maxNumberOfSamples) {
// The straightforward implementation can easily overflow:
// return arbitrary.exhaustive(maxNumberOfSamples);
Arbitrary current = base.get();
Optional> last = current.exhaustive(maxNumberOfSamples);
for (int i = 0; i < depth; i++) {
if (!last.isPresent()) {
return Optional.empty();
}
current = recur.apply(current);
last = current.exhaustive(maxNumberOfSamples);
}
return last;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
RecursiveArbitrary> that = (RecursiveArbitrary>) o;
if (depth != that.depth) return false;
if (!LambdaSupport.areEqual(base, that.base)) return false;
return LambdaSupport.areEqual(recur, that.recur);
}
@Override
public int hashCode() {
return HashCodeSupport.hash(base.getClass(), recur.getClass(), depth);
}
private Arbitrary iteratedArbitrary() {
// Real recursion can blow the stack
Arbitrary current = base.get();
isGeneratorMemoizable = current.isGeneratorMemoizable();
for (int i = 0; i < depth; i++) {
current = recur.apply(current);
if (!current.isGeneratorMemoizable()) {
isGeneratorMemoizable = false;
}
}
return current;
}
}