summaryrefslogtreecommitdiff
path: root/src/main/java/au/id/zancanaro/javacheck/ShrinkTree.java
blob: ddef8000ffa2755dd677c7bb2aaf01496d90729c (about) (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
package au.id.zancanaro.javacheck;

import java.io.IOException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.IntStream;
import java.util.stream.Stream;

@SuppressWarnings("unused")
public class ShrinkTree<T> {
    private final T value;
    private final Supplier<Stream<ShrinkTree<T>>> children;

    private ShrinkTree(T value, Supplier<Stream<ShrinkTree<T>>> children) {
        this.value = value;
        this.children = children;
    }

    public T getValue() {
        return value;
    }

    public Stream<ShrinkTree<T>> getChildren() {
        return children.get();
    }

    @SuppressWarnings("Convert2MethodRef")
    public static <T> ShrinkTree<T> pure(T value) {
        // Converting the () -> Stream.empty() into Stream::empty actually
        // breaks Java's generic type inference. Who would have thought?
        return new ShrinkTree<>(value, () -> Stream.empty());
    }

    public static <T> ShrinkTree<T> join(ShrinkTree<ShrinkTree<T>> tree) {
        return new ShrinkTree<>(
                tree.getValue().getValue(),
                () -> Stream.concat(
                        tree.getChildren().map(ShrinkTree::join),
                        tree.getValue().getChildren()));
    }

    private static <T> List<T> makeHeadList(ShrinkTree<T>[] trees) {
        List<T> heads = new ArrayList<>(trees.length);
        for (ShrinkTree<T> tree : trees) {
            heads.add(tree.getValue());
        }
        return heads;
    }

    @SuppressWarnings({"unchecked", "SuspiciousUninitializedArray"})
    public static <T> Stream<ShrinkTree<T>[]> promoteChildren(ShrinkTree<? super T>[] trees) {
        return IntStream.range(0, trees.length)
                .mapToObj(index -> trees[index].getChildren().map(child ->
                        (ShrinkTree<T>[]) IntStream.range(0, trees.length)
                                .mapToObj(i -> (i == index ? child : trees[i]))
                                .toArray(ShrinkTree[]::new)))
                .flatMap(x -> x);
    }

    @SuppressWarnings("unchecked")
    public static <T> Stream<ShrinkTree<T>[]> removeChildren(ShrinkTree<? super T>[] trees) {
        return IntStream.range(0, trees.length)
                .mapToObj(index ->
                        (ShrinkTree<T>[]) IntStream.range(0, trees.length)
                                .filter(i -> i != index)
                                .mapToObj(i -> trees[i])
                                .toArray(ShrinkTree[]::new));
    }

    public static <T> Stream<ShrinkTree<T>[]> removeAndPromoteChildren(ShrinkTree<? super T>[] trees) {
        return Stream.concat(removeChildren(trees), promoteChildren(trees));
    }

    public static <T> ShrinkTree<List<T>> combine(
            ShrinkTree<T>[] trees,
            Function<ShrinkTree<T>[], Stream<ShrinkTree<T>[]>> processChildren) {
        return new ShrinkTree<>(
                makeHeadList(trees),
                () -> processChildren.apply(trees)
                        .map(shrinks -> combine(shrinks, processChildren)));
    }

    public <R> ShrinkTree<R> map(Function<? super T, ? extends R> f) {
        return new ShrinkTree<>(
                f.apply(this.value),
                () -> this.getChildren().map(tree -> tree.map(f)));
    }

    public <R> ShrinkTree<R> flatMap(Function<? super T, ? extends ShrinkTree<R>> f) {
        return ShrinkTree.join(this.map(f));
    }

    public ShrinkTree<T> filter(Predicate<? super T> predicate) {
        T value = this.getValue();
        if (predicate.test(value)) {
            return new ShrinkTree<>(
                    value,
                    () -> this.getChildren()
                            .filter(tree -> predicate.test(tree.getValue()))
                            .map(tree -> tree.filter(predicate)));
        } else {
            throw new IllegalArgumentException("Current value doesn't match predicate: whoops!");
        }
    }

    public ShrinkTree<T> withShrinkStrategy(ShrinkStrategy<T> strategy) {
        return new ShrinkTree<>(this.getValue(), () -> strategyStream(this.getValue(), strategy));
    }

    private static <T> Stream<ShrinkTree<T>> strategyStream(final T value, final ShrinkStrategy<T> strategy) {
        return strategy.shrink(value)
                .map(v -> new ShrinkTree<>(v, () -> strategyStream(v, strategy)));
    }

    public void print(Writer output) throws IOException {
        print(output, Object::toString);
    }

    public void print(Writer output, Function<? super T, ? extends String> toString) throws IOException {
        output.write(toString.apply(this.getValue()));
        output.write('[');
        Iterator<ShrinkTree<T>> iterator = children.get().iterator();
        while (iterator.hasNext()) {
            iterator.next().print(output, toString);
            if (iterator.hasNext()) {
                output.write(' ');
            }
        }
        output.write(']');
        output.flush();
    }
}