net.jqwik.engine.properties.DomainContextBaseProviders Maven / Gradle / Ivy
package net.jqwik.engine.properties;
import java.lang.reflect.*;
import java.util.*;
import java.util.function.*;
import java.util.logging.*;
import java.util.stream.*;
import org.junit.platform.commons.support.*;
import net.jqwik.api.*;
import net.jqwik.api.domains.*;
import net.jqwik.api.providers.*;
import net.jqwik.api.providers.ArbitraryProvider.*;
import net.jqwik.engine.support.*;
import static net.jqwik.engine.support.JqwikReflectionSupport.*;
public class DomainContextBaseProviders {
private static final Logger LOG = Logger.getLogger(DomainContextBaseProviders.class.getName());
static public Collection forContextBase(DomainContextBase base, int priority) {
return JqwikStreamSupport.concat(
providersFromProviderMethods(base, priority),
providersFromInnerClasses(base, priority),
providersFromBaseItself(base, priority)
).collect(Collectors.toList());
}
private static Stream providersFromProviderMethods(DomainContextBase base, int priority) {
List methods = AnnotationSupport.findAnnotatedMethods(base.getClass(), Provide.class, HierarchyTraversalMode.BOTTOM_UP);
warnIfMethodsHaveWrongReturnType(methods);
warnIfProvideAnnotationHasValue(methods);
return methods.stream()
.filter(method -> isArbitrary(method.getReturnType()))
.map(method -> new MethodBaseArbitraryProvider(method, base, priority));
}
private static void warnIfProvideAnnotationHasValue(List methods) {
methods.stream()
.filter(method -> isArbitrary(method.getReturnType()))
.map(method -> Tuple.of(method, AnnotationSupport.findAnnotation(method, Provide.class)))
.filter(methodAndProvide -> methodAndProvide.get2().map(a -> !a.value().isEmpty()).orElse(false))
.forEach(methodAndProvide -> {
String message = String.format(
"Method %s is annotated with %s but having a value does not make sense in a domain context.",
methodAndProvide.get1(),
methodAndProvide.get2().get()
);
LOG.warning(message);
});
}
private static void warnIfMethodsHaveWrongReturnType(List methods) {
methods.stream()
.filter(method -> !isArbitrary(method.getReturnType()))
.forEach(method -> {
String message = String.format("Method %s is annotated with @Provide but does not return an Arbitrary subtype.", method);
LOG.warning(message);
});
}
private static Stream providersFromInnerClasses(DomainContextBase base, int priority) {
Predicate> implementsArbitraryProvider =
clazz -> ArbitraryProvider.class.isAssignableFrom(clazz) && !JqwikReflectionSupport.isPrivate(clazz);
List> arbitraryProviderClasses = ReflectionSupport.findNestedClasses(base.getClass(), implementsArbitraryProvider);
warnIfClassesHaveNoFittingConstructor(arbitraryProviderClasses);
return arbitraryProviderClasses.stream()
.filter(DomainContextBaseProviders::hasFittingConstructor)
.map(clazz -> createArbitraryProvider(clazz, base, priority));
}
private static Stream providersFromBaseItself(DomainContextBase base, int priority) {
if (base instanceof ArbitraryProvider) {
return Stream.of(new ArbitraryProviderWithPriority((ArbitraryProvider) base, priority));
}
return Stream.empty();
}
private static void warnIfClassesHaveNoFittingConstructor(List> classes) {
classes.stream()
.filter(aClass -> !hasFittingConstructor(aClass))
.forEach(DomainContextBaseProviders::warnThatNoDefaultConstructorPresent);
}
private static void warnThatNoDefaultConstructorPresent(Class> aClass) {
String message = String.format(
"Class <%s> does not have a default constructor and cannot be instantiated as %s.",
aClass.getName(),
ArbitraryProvider.class
);
LOG.warning(message);
}
private static boolean hasFittingConstructor(Class> clazz) {
if (JqwikReflectionSupport.isStatic(clazz)) {
return hasDefaultConstructor(clazz);
}
return hasConstructor(clazz, clazz.getDeclaringClass());
}
private static ArbitraryProvider createArbitraryProvider(Class> clazz, DomainContextBase base, int priority) {
ArbitraryProvider arbitraryProviderInstance = (ArbitraryProvider) JqwikReflectionSupport.newInstanceInTestContext(clazz, base);
if (JqwikReflectionSupport.implementsMethod(clazz, "priority", new Class[0], ArbitraryProvider.class)) {
return arbitraryProviderInstance;
}
return new ArbitraryProviderWithPriority(arbitraryProviderInstance, priority);
}
private static boolean isArbitrary(Class> type) {
return Arbitrary.class.isAssignableFrom(type);
}
private static class ArbitraryProviderWithPriority implements ArbitraryProvider {
private final ArbitraryProvider instance;
private final int priority;
private ArbitraryProviderWithPriority(ArbitraryProvider instance, int priority) {
this.instance = instance;
this.priority = priority;
}
@Override
public boolean canProvideFor(TypeUsage targetType) {
return instance.canProvideFor(targetType);
}
@Override
public Set> provideFor(TypeUsage targetType, SubtypeProvider subtypeProvider) {
return instance.provideFor(targetType, subtypeProvider);
}
@Override
public int priority() {
return priority;
}
}
private static class DomainContextBaseSubtypeProvider extends InstanceBasedSubtypeProvider {
private final SubtypeProvider baseProvider;
protected DomainContextBaseSubtypeProvider(Object base, SubtypeProvider baseProvider) {
super(base);
this.baseProvider = baseProvider;
}
@Override
protected Set> resolve(TypeUsage parameterType) {
return baseProvider.apply(parameterType);
}
@Override
protected Arbitrary> configure(Arbitrary> arbitrary, TypeUsage targetType) {
// TODO: Implement configuration
return arbitrary;
}
}
private static class MethodBaseArbitraryProvider implements ArbitraryProvider {
private MethodBaseArbitraryProvider(Method method, Object base, int priority) {
this.method = method;
this.base = base;
this.priority = priority;
}
private final Method method;
private final Object base;
private final int priority;
@Override
public boolean canProvideFor(TypeUsage targetType) {
return targetTypeFits(targetType);
}
@Override
public Set> provideFor(TypeUsage targetType, SubtypeProvider subtypeProvider) {
SubtypeProvider domainSubtypeProvider = new net.jqwik.engine.properties.DomainContextBaseProviders.DomainContextBaseSubtypeProvider(base, subtypeProvider);
return new ProviderMethodInvoker(method, targetType, base, domainSubtypeProvider).invoke();
}
@Override
public int priority() {
return priority;
}
private boolean targetTypeFits(TypeUsage targetType) {
TypeUsage arbitraryReturnType = arbitraryReturnType();
// This can lead to arbitraries being applied where they don't fit due to parameterized inner types
return arbitraryReturnType.canBeAssignedTo(targetType) || targetType.canBeAssignedTo(arbitraryReturnType);
}
private TypeUsage arbitraryReturnType() {
TypeUsage arbitraryType = arbitraryType(TypeUsage.forType(method.getGenericReturnType()));
return arbitraryType.getTypeArgument(0);
}
private TypeUsage arbitraryType(TypeUsage baseType) {
if (!baseType.isOfType(Arbitrary.class)) {
for (TypeUsage anInterface : baseType.getInterfaces()) {
if (isArbitrary(anInterface.getRawType())) {
return arbitraryType(anInterface);
}
}
}
return baseType;
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy