package au.id.zancanaro.javacheck; import java.io.IOException; import java.io.Writer; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.function.Function; import java.util.function.Predicate; import static au.id.zancanaro.javacheck.Iterators.*; @SuppressWarnings("unused") public class ShrinkTree { private final T value; private final Iterable> children; private ShrinkTree(T value, Iterable> children) { this.value = value; this.children = children; } public T getValue() { return value; } public Iterator> getChildren() { return children.iterator(); } public static ShrinkTree pure(T value) { return new ShrinkTree<>(value, Collections.emptyList()); } public static ShrinkTree join(ShrinkTree> tree) { return new ShrinkTree<>( tree.getValue().getValue(), () -> concat( mappingIterator(ShrinkTree::join, tree.children.iterator()), tree.getValue().children.iterator())); } private static Iterator[]> permutations(ShrinkTree[] trees) { return flatten( rangeIterator(trees.length, index -> mappingIterator(child -> { @SuppressWarnings("unchecked") ShrinkTree[] result = (ShrinkTree[]) new ShrinkTree[trees.length]; for (int i = 0; i < trees.length; ++i) { result[i] = (i == index ? child : trees[i]); } return result; }, trees[index].getChildren()) )); } private static List makeHeadList(ShrinkTree[] trees) { List heads = new ArrayList<>(trees.length); for (ShrinkTree tree : trees) { heads.add(tree.getValue()); } return heads; } public static ShrinkTree zip(Function, R> fn, ShrinkTree[] trees) { return new ShrinkTree<>( fn.apply(makeHeadList(trees)), () -> mappingIterator( shrinks -> ShrinkTree.zip(fn, shrinks), ShrinkTree.permutations(trees))); } private static Iterator[]> removeEach(ShrinkTree[] trees) { return concat( rangeIterator(trees.length, index -> { @SuppressWarnings("unchecked") ShrinkTree[] result = (ShrinkTree[]) new ShrinkTree[trees.length - 1]; for (int i = 0; i < trees.length - 1; ++i) { result[i] = trees[(i >= index ? i + 1 : i)]; } return result; }), permutations(trees)); } public static ShrinkTree shrink(Function, R> fn, ShrinkTree[] trees) { return new ShrinkTree<>( fn.apply(makeHeadList(trees)), () -> mappingIterator( shrinks -> ShrinkTree.shrink(fn, shrinks), ShrinkTree.removeEach(trees))); } public ShrinkTree map(Function f) { return new ShrinkTree<>( f.apply(this.value), () -> mappingIterator(tree -> tree.map(f), this.children.iterator())); } public ShrinkTree flatMap(Function> f) { return ShrinkTree.join(this.map(f)); } public ShrinkTree filter(Predicate predicate) { if (predicate.test(this.getValue())) { return new ShrinkTree<>( this.getValue(), () -> mappingIterator(tree -> tree.filter(predicate), filteringIterator( tree -> predicate.test(tree.getValue()), this.getChildren()))); } else { throw new IllegalArgumentException("Current value doesn't match predicate: whoops!"); } } public ShrinkTree withShrinkStrategy(ShrinkStrategy strategy) { return new ShrinkTree<>(this.getValue(), strategyIterable(this.getValue(), strategy)); } private static Iterable> strategyIterable(final T value, final ShrinkStrategy strategy) { return () -> mappingIterator( v -> new ShrinkTree<>(v, strategyIterable(v, strategy)), strategy.shrink(value)); } @SuppressWarnings("unused") public void print(Writer output) throws IOException { print(output, Object::toString); } @SuppressWarnings("unused") public void print(Writer output, Function toString) throws IOException { output.write(toString.apply(this.getValue())); output.write('['); for (ShrinkTree child : children) { child.print(output, toString); } output.write(']'); output.flush(); } }