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 net.jqwik.api.*;
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;

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

	private C createValue(List> shrinkables) {
		return shrinkables
					   .stream()
					   .map(Shrinkable::value)
					   .collect(containerCollector());
	}

	@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) {
			List> removedShrinkables = new ArrayList<>(((ShrinkableContainer) before).elements);
			removedShrinkables.removeAll(((ShrinkableContainer) after).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() {
		return Combinatorics
					   .distinctPairs(elements.size())
					   .flatMap(pair -> JqwikStreamSupport.zip(
							   elements.get(pair.get1()).shrink(),
							   elements.get(pair.get2()).shrink(),
							   (Shrinkable s1, Shrinkable s2) -> {
								   List> newElements = new ArrayList<>(elements);
								   newElements.set(pair.get1(), s1);
								   newElements.set(pair.get2(), s2);
								   if (checkUniquenessOfShrinkables(uniquenessExtractors, newElements)) {
									   return createShrinkable(newElements);
								   } else {
									   // null value will skip the entry in zipped stream
									   return null;
								   }
							   }
					   ));
	}

	protected Stream> sortElements() {
		return ShrinkingCommons.sortElements(elements, 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 Objects.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);

	abstract Collector containerCollector();

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy