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

net.jqwik.engine.properties.shrinking.ShrinkableContainer Maven / Gradle / Ivy

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

import java.util.*;
import java.util.stream.*;

import org.jspecify.annotations.*;

import net.jqwik.api.*;
import net.jqwik.api.support.*;
import net.jqwik.engine.properties.*;
import net.jqwik.engine.support.*;

import static net.jqwik.engine.properties.UniquenessChecker.*;

abstract class ShrinkableContainer implements Shrinkable {
	protected final List> elements;
	protected final int minSize;
	protected final int maxSize;
	protected final Collection> uniquenessExtractors;

	protected final @Nullable Arbitrary elementArbitrary;

	ShrinkableContainer(
		List> elements,
		int minSize, int maxSize,
		Collection> uniquenessExtractors,
		@Nullable Arbitrary elementArbitrary
	) {
		this.elements = elements;
		this.minSize = minSize;
		this.maxSize = maxSize;
		this.uniquenessExtractors = uniquenessExtractors;
		this.elementArbitrary = elementArbitrary;
	}

	abstract C createValue(List> shrinkables);

	@Override
	public C value() {
		return createValue(elements);
	}

	@Override
	public Stream> shrink() {
		return JqwikStreamSupport.concat(
			shrinkSizeOfList(),
			shrinkElementsOneAfterTheOther(0),
			shrinkPairsOfElements()
		);
	}

	@Override
	public Optional> grow(Shrinkable before, Shrinkable after) {
		if (before instanceof ShrinkableContainer && after instanceof ShrinkableContainer) {
			ShrinkableContainer beforeContainer = (ShrinkableContainer) before;
			ShrinkableContainer afterContainer = (ShrinkableContainer) after;
			// Moving shrinkable from one container to another is only allowed if both contain elements
			// created by the same arbitrary
			if (Objects.equals(beforeContainer.elementArbitrary, afterContainer.elementArbitrary)) {
				List> removedShrinkables = new ArrayList<>((beforeContainer).elements);
				removedShrinkables.removeAll((afterContainer).elements);
				return growBy(removedShrinkables);
			}
		}
		return Optional.empty();
	}

	@Override
	public Stream> grow() {
		return growOneElementAfterTheOther();
	}

	private Stream> growOneElementAfterTheOther() {
		List>> growPerElementStreams = new ArrayList<>();
		for (int i = 0; i < elements.size(); i++) {
			int index = i;
			Shrinkable element = elements.get(i);
			Stream> shrinkElement = element.grow().flatMap(shrunkElement -> {
				List> elementsCopy = new ArrayList<>(elements);
				elementsCopy.set(index, shrunkElement);
				return Stream.of(createShrinkable(elementsCopy));
			});
			growPerElementStreams.add(shrinkElement);
		}
		return JqwikStreamSupport.concat(growPerElementStreams);
	}

	private Optional> growBy(List> shrinkables) {
		if (elements.size() + shrinkables.size() <= maxSize) {
			List> grownElements = new ArrayList<>(elements);
			for (Shrinkable shrinkable : shrinkables) {
				try {
					@SuppressWarnings("unchecked")
					Shrinkable castShrinkable = (Shrinkable) shrinkable;
					grownElements.add(0, castShrinkable);
				} catch (Throwable classCastException) {
					return Optional.empty();
				}
			}
			Shrinkable grownShrinkable = createShrinkable(grownElements);
			if (hasReallyGrown(grownShrinkable)) {
				return Optional.of(grownShrinkable);
			}
		}
		return Optional.empty();
	}

	protected boolean hasReallyGrown(Shrinkable grownShrinkable) {
		return true;
	}

	protected Stream> shrinkSizeAggressively() {
		return new AggressiveSizeOfListShrinker>(minSize)
				   .shrink(elements)
				   .map(this::createShrinkable)
				   .sorted(Comparator.comparing(Shrinkable::distance));
	}

	protected Stream> shrinkSizeOfList() {
		return new SizeOfListShrinker>(minSize)
				   .shrink(elements)
				   .map(this::createShrinkable)
				   .sorted(Comparator.comparing(Shrinkable::distance));
	}

	protected Stream> shrinkElementsOneAfterTheOther(int maxToShrink) {
		List>> shrinkPerElementStreams = new ArrayList<>();
		for (int i = 0; i < elements.size(); i++) {
			if (maxToShrink > 0 && i >= maxToShrink) {
				break;
			}
			int index = i;
			Shrinkable element = elements.get(i);
			Stream> shrinkElement = element.shrink().flatMap(shrunkElement -> {
				List> elementsCopy = new ArrayList<>(elements);
				elementsCopy.remove(index);
				if (!checkShrinkableUniqueIn(uniquenessExtractors, shrunkElement, elementsCopy)) {
					return Stream.empty();
				}
				elementsCopy.add(index, shrunkElement);
				return Stream.of(createShrinkable(elementsCopy));
			});
			shrinkPerElementStreams.add(shrinkElement);
		}
		return JqwikStreamSupport.concat(shrinkPerElementStreams);
	}

	protected Stream> shrinkPairsOfElements() {
		ShrinkingCommons.ContainerCreator createContainer = newElements -> {
			if (checkUniquenessOfShrinkables(uniquenessExtractors, newElements)) {
				return createShrinkable(newElements);
			} else {
				// null value will skip the entry in zipped stream
				return null;
			}
		};
		return ShrinkingCommons.shrinkPairsOfElements(elements, createContainer);
	}

	protected Stream> sortElements() {
		return ShrinkingCommons.sortElements(elements, (ShrinkingCommons.ContainerCreator) this::createShrinkable);
	}

	@Override
	public ShrinkingDistance distance() {
		return ShrinkingDistance.forCollection(elements);
	}

	@Override
	public boolean equals(Object o) {
		if (this == o) return true;
		if (o == null || getClass() != o.getClass()) return false;
		ShrinkableContainer that = (ShrinkableContainer) o;
		return value().equals(that.value());
	}

	@Override
	public int hashCode() {
		return HashCodeSupport.hash(elements);
	}

	@Override
	public String toString() {
		return String.format(
			"%s<%s>(%s:%s)",
			getClass().getSimpleName(),
			value().getClass().getSimpleName(),
			value(), distance()
		);
	}

	abstract Shrinkable createShrinkable(List> shrunkElements);

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy