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

net.jqwik.engine.properties.configurators.UniqueElementsConfigurator Maven / Gradle / Ivy

The newest version!
package net.jqwik.engine.properties.configurators;

import java.util.*;
import java.util.concurrent.*;
import java.util.function.*;
import java.util.stream.*;

import net.jqwik.api.*;
import net.jqwik.api.arbitraries.*;
import net.jqwik.api.configurators.*;
import net.jqwik.api.constraints.*;
import net.jqwik.api.providers.*;
import net.jqwik.engine.support.*;

import org.jspecify.annotations.*;

@SuppressWarnings("unchecked")
public class UniqueElementsConfigurator implements ArbitraryConfigurator {

	@SuppressWarnings({"OverlyComplexMethod", "OverlyLongMethod"})
	@Override
	public  Arbitrary configure(Arbitrary arbitrary, TypeUsage targetType) {
		return targetType.findAnnotation(UniqueElements.class).map(uniqueness -> {
			if (arbitrary instanceof SetArbitrary) {
				// Handle SetArbitrary explicitly for optimization
				return (Arbitrary) configureSetArbitrary((SetArbitrary) arbitrary, uniqueness);
			}
			if (arbitrary instanceof StreamableArbitrary) {
				return (Arbitrary) configureStreamableArbitrary((StreamableArbitrary) arbitrary, uniqueness);
			}
			if (targetType.isAssignableFrom(List.class)) {
				Arbitrary> listArbitrary = (Arbitrary>) arbitrary;
				return (Arbitrary) listArbitrary.filter(isUnique(uniqueness));
			}
			if (targetType.isAssignableFrom(Set.class)) {
				Arbitrary> setArbitrary = (Arbitrary>) arbitrary;
				return (Arbitrary) setArbitrary.filter(isUnique(uniqueness));
			}
			if (targetType.isArray()) {
				Arbitrary arrayArbitrary = (Arbitrary) arbitrary;
				Predicate> predicate = isUnique(uniqueness);
				return (Arbitrary) arrayArbitrary.filter(array -> predicate.test(Arrays.asList(array)));
			}
			if (targetType.isAssignableFrom(Stream.class)) {
				Arbitrary> streamArbitrary = (Arbitrary>) arbitrary;
				// Since a stream can only be consumed once this is more involved than seems necessary at first glance
				return (Arbitrary) streamArbitrary.map(s -> s.collect(Collectors.toList()))
													 .filter(isUnique(uniqueness))
													 .map(Collection::stream);
			}
			if (targetType.isAssignableFrom(Iterator.class)) {
				Arbitrary> iteratorArbitrary = (Arbitrary>) arbitrary;
				// Since an iterator can only be iterated once this is more involved than seems necessary at first glance
				Arbitrary> listArbitrary = iteratorArbitrary.map(this::toList);
				return (Arbitrary) listArbitrary.filter(isUnique(uniqueness)).map(List::iterator);
			}
			return arbitrary;
		}).orElse(arbitrary);
	}

	private  List toList(Iterator i) {
		List list = new ArrayList<>();
		while (i.hasNext()) {
			list.add(i.next());
		}
		return list;
	}

	@SuppressWarnings("OverlyLongMethod")
	private static > Predicate isUnique(UniqueElements uniqueness) {
		Class> extractorClass = uniqueness.by();
		if (extractorClass.equals(UniqueElements.NOT_SET.class)) {
			return items -> {
				// Intentionally uses `items.getClass().equals(HashSet.class)`
				// instead of `items instanceof HashSet`, because subclasses
				// of HashSet may break Set conventions.
				Class c = items.getClass();
				if (c.equals(HashSet.class) ||
						c.equals(LinkedHashSet.class) ||
						c.equals(ConcurrentHashMap.KeySetView.class) ||
						c.equals(CopyOnWriteArraySet.class))
					return true;
				Set set = new HashSet<>();
				for (Object x : items) {
					if (!set.add(x)) {
						return false;
					}
				}
				return true;
			};
		}
		Function extractor = extractor(extractorClass);
		return items -> {
			Set set = new HashSet<>();
			for (Object x : items) {
				if (!set.add(extractor.apply(x))) {
					return false;
				}
			}
			return true;
		};
	}

	private  Arbitrary configureStreamableArbitrary(StreamableArbitrary arbitrary, UniqueElements uniqueness) {
		Class> extractorClass = uniqueness.by();
		if (extractorClass.equals(UniqueElements.NOT_SET.class)) {
			return arbitrary.uniqueElements();
		}
		Function extractor = extractor(extractorClass);
		return arbitrary.uniqueElements(extractor);
	}

	private  Arbitrary configureSetArbitrary(SetArbitrary arbitrary, UniqueElements uniqueness) {
		Class> extractorClass = uniqueness.by();
		if (extractorClass.equals(UniqueElements.NOT_SET.class)) {
			return arbitrary;
		}
		Function extractor = extractor(extractorClass);
		return arbitrary.uniqueElements(extractor);
	}

	private static  Function extractor(Class> extractorClass) {
		return (Function) (
			extractorClass.equals(UniqueElements.NOT_SET.class)
				? Function.identity()
				// TODO: Create instance in context of test instance.
				//       This requires an extension of ArbitraryConfiguration interface
				//       to provide access to PropertyLifecycleContext
				: JqwikReflectionSupport.newInstanceWithDefaultConstructor(extractorClass)
		);
	}

}