net.jqwik.engine.properties.arbitraries.DefaultTraverseArbitrary Maven / Gradle / Ivy
package net.jqwik.engine.properties.arbitraries;
import java.lang.reflect.*;
import java.util.*;
import java.util.function.*;
import java.util.stream.*;
import org.jspecify.annotations.*;
import org.junit.platform.commons.support.*;
import net.jqwik.api.*;
import net.jqwik.api.arbitraries.*;
import net.jqwik.api.providers.*;
import net.jqwik.api.support.*;
import net.jqwik.engine.support.types.*;
import static org.junit.platform.commons.support.ModifierSupport.*;
import static net.jqwik.engine.support.JqwikReflectionSupport.*;
public class DefaultTraverseArbitrary extends ArbitraryDecorator implements TraverseArbitrary {
private final Class targetType;
private final Traverser traverser;
private final Map> arbitrariesCache;
private boolean enableRecursion = false;
public DefaultTraverseArbitrary(Class targetType, Traverser traverser) {
this(targetType, traverser, new LinkedHashMap<>());
}
private DefaultTraverseArbitrary(Class targetType, Traverser traverser, Map> arbitrariesCache) {
this.targetType = targetType;
this.traverser = traverser;
this.arbitrariesCache = arbitrariesCache;
}
@Override
protected Arbitrary arbitrary() {
TypeUsage targetTypeUsage = TypeUsage.forType(targetType);
List> arbitraries = streamCreators(targetTypeUsage)
.map(this::createArbitrary)
.collect(Collectors.toList());
if (arbitraries.isEmpty()) {
String message = String.format(
"No usable generator executables (constructors or factory methods) " +
"have been provided for type [%s].",
targetType
);
throw new JqwikException(message);
}
return Arbitraries.oneOf(arbitraries);
}
private Stream streamCreators(TypeUsage targetTypeUsage) {
Set creators = traverser.findCreators(targetTypeUsage);
return creators
.stream()
.filter(this::constructorIsConcrete)
.filter(this::methodIsStatic)
.filter(this::isNotRecursive)
.map(this::checkFittingReturnType);
}
private boolean constructorIsConcrete(Executable executable) {
if (executable instanceof Constructor) {
Constructor> ctor = (Constructor>) executable;
return !isAbstract(targetType);
}
return true;
}
private boolean methodIsStatic(Executable executable) {
if (executable instanceof Method) {
Method method = (Method) executable;
return ModifierSupport.isStatic(method);
}
return true;
}
@Override
public TraverseArbitrary enableRecursion() {
DefaultTraverseArbitrary clone = typedClone();
clone.enableRecursion = true;
return clone;
}
private Executable checkFittingReturnType(Executable creator) {
TypeUsage returnType = TypeUsage.forType(creator.getAnnotatedReturnType().getType());
if (!returnType.canBeAssignedTo(TypeUsage.of(targetType))) {
throw new JqwikException(String.format("%s should return type assignable to %s", creator, targetType));
}
return creator;
}
private boolean isNotRecursive(Executable creator) {
// TODO: Check for real recursiveness not just direct recursive calls
return Arrays.stream(creator.getParameterTypes()).noneMatch(parameterType -> parameterType.equals(targetType));
}
@Override
public String toString() {
return String.format("TraverseArbitrary<%s>(allowRecursion=%s)", targetType.getName(), enableRecursion);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
DefaultTraverseArbitrary> that = (DefaultTraverseArbitrary>) o;
if (enableRecursion != that.enableRecursion) return false;
if (!targetType.equals(that.targetType)) return false;
return LambdaSupport.areEqual(traverser, that.traverser);
}
@Override
public int hashCode() {
return HashCodeSupport.hash(targetType, enableRecursion);
}
private Arbitrary createArbitrary(
Executable creator
) {
List> parameterArbitraries =
getMethodParameters(creator, targetType)
.stream()
.map(methodParameter -> arbitraryFor(TypeUsageImpl.forParameter(methodParameter)))
.collect(Collectors.toList());
Function, T> combinator = paramList -> combinator(creator).apply(paramList.toArray());
Arbitrary arbitrary = Combinators.combine(parameterArbitraries).as(combinator);
return arbitrary.ignoreException(GenerationError.class);
}
private Arbitrary