001/*
002 * Copyright © 2025 CUI-OpenSource-Software (info@cuioss.de)
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package de.cuioss.test.generator.internal.net.java.quickcheck.generator.support;
017
018import de.cuioss.test.generator.internal.net.java.quickcheck.Generator;
019import de.cuioss.test.generator.internal.net.java.quickcheck.StatefulGenerator;
020
021import java.util.Iterator;
022import java.util.Objects;
023
024import static de.cuioss.test.generator.internal.net.java.quickcheck.generator.iterable.Iterables.sizeOf;
025
026public class EnsuredValuesGenerator<T> implements StatefulGenerator<T> {
027
028    private final Iterable<T> ensured;
029    private final Generator<T> otherValues;
030    private Iterator<T> iterator;
031    private final int size;
032    private final int window;
033    private int valuesLeft;
034    private int generatesLeft;
035
036    public EnsuredValuesGenerator(Iterable<T> values) {
037        this(values, new FixedValuesGenerator<>(values));
038    }
039
040    public EnsuredValuesGenerator(Iterable<T> ensured, Generator<T> random) {
041        this(ensured, sizeOf(ensured), random);
042    }
043
044    public EnsuredValuesGenerator(Iterable<T> ensured, int window, Generator<T> random) {
045        this.size = sizeOf(ensured);
046        if (this.size > window) {
047            throw new IllegalArgumentException("window");
048        }
049        this.window = window;
050        Objects.requireNonNull(random, "random");
051        this.ensured = ensured;
052        this.otherValues = random;
053
054        reset();
055    }
056
057    @Override
058    public T next() {
059        return takeEnsured() ? iterator.next() : otherValues.next();
060    }
061
062    private boolean takeEnsured() {
063        if (valuesLeft > 0 && spreadOverWindow()) {
064            valuesLeft--;
065            return true;
066        }
067        generatesLeft--;
068        return false;
069    }
070
071    /**
072     * @return true if an ensured values should be taken. The results should be such
073     *         that the ensured values are uniformly distributed over the window.
074     */
075    private boolean spreadOverWindow() {
076        assert valuesLeft > 0 && generatesLeft >= 0;
077        return new IntegerGenerator(0, valuesLeft + generatesLeft).next() <= valuesLeft;
078    }
079
080    @Override
081    public void reset() {
082        iterator = ensured.iterator();
083        valuesLeft = size;
084        generatesLeft = window - size;
085        assert generatesLeft >= 0 : generatesLeft;
086    }
087}