All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.jqwik.engine.properties.ProviderMethodInvoker Maven / Gradle / Ivy

There is a newer version: 1.9.1
Show newest version
package net.jqwik.engine.properties;

import java.lang.reflect.*;
import java.util.*;
import java.util.function.*;
import java.util.stream.*;

import net.jqwik.api.*;
import net.jqwik.api.providers.ArbitraryProvider.*;
import net.jqwik.api.providers.*;
import net.jqwik.engine.support.*;
import net.jqwik.engine.support.types.*;

import static net.jqwik.engine.support.JqwikReflectionSupport.*;

class ProviderMethodInvoker {

	ProviderMethodInvoker(Method providerMethod, TypeUsage targetType, Object instance, SubtypeProvider subtypeProvider) {
		this.providerMethod = providerMethod;
		this.targetType = targetType;
		this.instance = instance;
		this.subtypeProvider = subtypeProvider;
	}

	private final Method providerMethod;
	private final TypeUsage targetType;
	private final Object instance;
	private final SubtypeProvider subtypeProvider;

	Set> invoke() {
		List parameters = JqwikReflectionSupport.getMethodParameters(providerMethod, instance.getClass());
		Set, Arbitrary>> baseInvoker = Collections.singleton(this::invokeProviderMethod);
		Set>> suppliers = arbitrarySuppliers(baseInvoker, parameters, Collections.emptyList());
		return mapSet(suppliers, Supplier::get);
	}

	private Arbitrary invokeProviderMethod(List argList) {
		return (Arbitrary) invokeMethodPotentiallyOuter(providerMethod, instance, argList.toArray());
	}

	private Set>> arbitrarySuppliers(
		Set, Arbitrary>> invokers,
		List unresolvedParameters,
		List args
	) {
		if (unresolvedParameters.isEmpty()) {
			return mapSet(invokers, invoker -> () -> invoker.apply(args));
		}
		List newUnresolvedParameters = new ArrayList<>(unresolvedParameters);
		MethodParameter toResolve = newUnresolvedParameters.remove(0);
		if (isForAllParameter(toResolve)) {
			List newArgs = new ArrayList<>(args);
			newArgs.add(arbitraryFor(toResolve)); // Arbitrary is now in position toResolve.getIndex()
			Set, Arbitrary>> newInvokers = flatMapArbitraryInInvocations(invokers, toResolve.getIndex());
			return arbitrarySuppliers(newInvokers, newUnresolvedParameters, newArgs);
		} else {
			List newArgs = new ArrayList<>(args);
			newArgs.add(resolvePlainParameter(toResolve.getRawParameter()));
			return arbitrarySuppliers(invokers, newUnresolvedParameters, newArgs);
		}
	}

	private Set, Arbitrary>> flatMapArbitraryInInvocations(Set, Arbitrary>> invokers, int position) {
		Function, Arbitrary>, Function, Arbitrary>> mapper = invoker -> arguments -> {
			Arbitrary a = (Arbitrary) arguments.get(position);
			return a.flatMap(argument -> {
				List resolved = new ArrayList<>(arguments);
				resolved.set(position, argument);
				return invoker.apply(resolved);
			});
		};
		return mapSet(invokers, mapper);
	}

	private Arbitrary arbitraryFor(MethodParameter toResolve) {
		TypeUsage parameterType = TypeUsageImpl.forParameter(toResolve);
		Optional> optionalArbitrary = subtypeProvider.provideOneFor(parameterType);
		return optionalArbitrary.orElseThrow(
			() ->
				new CannotFindArbitraryException(
					parameterType,
					parameterType.findAnnotation(ForAll.class).orElse(null),
					providerMethod
				)
		);
	}

	private  Set mapSet(Set invokers, Function mapper) {
		return invokers.stream().map(mapper).collect(Collectors.toSet());
	}

	private boolean isForAllParameter(MethodParameter parameter) {
		return parameter.isAnnotated(ForAll.class);
	}

	protected Object resolvePlainParameter(Parameter parameter) {
		if (parameter.getType().isAssignableFrom(TypeUsage.class)) {
			return targetType;
		} else {
			String message = String.format(
				"Parameter [%s] cannot be resolved in @Provide method [%s]." +
					"%nMaybe you want to add annotation `@ForAll`?",
				parameter,
				providerMethod
			);
			throw new JqwikException(message);
		}
	}
}