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

com.github.robtimus.io.stream.RandomInputStream Maven / Gradle / Ivy

Go to download

A collection of InputStream, OutputStream, Reader and Writer implementations

The newest version!
/*
 * RandomInputStream.java
 * Copyright 2020 Rob Spoor
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.github.robtimus.io.stream;

import static com.github.robtimus.io.stream.StreamUtils.checkOffsetAndLength;
import java.io.InputStream;
import java.security.SecureRandom;
import java.util.Objects;
import java.util.Random;
import java.util.function.Function;
import java.util.function.ToIntFunction;
import java.util.function.ToLongFunction;

/**
 * An input stream that will return random bytes.
 * This input stream supports {@link #mark(int)} and {@link #reset()}. It comes with a default mark at the start of the stream, which means that
 * {@link #reset()} can be called at any time.
 *
 * @author Rob Spoor
 */
public final class RandomInputStream extends InputStream {

    private final Random random;
    private final ToIntFunction byteGenerator;

    private final long limit;
    private long index;
    private long mark;

    private RandomInputStream(Builder builder) {
        this.random = builder.random != null ? builder.random : new SecureRandom();
        this.byteGenerator = builder.byteGenerator;

        this.limit = builder.limitGenerator != null ? builder.limitGenerator.applyAsLong(random) : builder.limit;
        this.index = 0;
        this.mark = 0;
    }

    @Override
    public int read() {
        if (limit >= 0 && index >= limit) {
            return -1;
        }
        index++;
        return nextByte() & 0xFF;
    }

    @Override
    public int read(byte[] cbuf) {
        return read(cbuf, 0, cbuf.length);
    }

    @Override
    public int read(byte[] b, int off, int len) {
        checkOffsetAndLength(b, off, len);

        if (limit >= 0 && index >= limit) {
            return -1;
        }
        int n = limit >= 0 ? (int) Math.min(limit - index, len) : len;
        for (int i = off, j = 0; j < n; i++, j++) {
            b[i] = nextByte();
        }
        index += n;
        return n;
    }

    @Override
    public long skip(long n) {
        if (n <= 0) {
            return 0;
        }

        if (limit < 0) {
            index += n;
            return n;
        }

        if (index >= limit) {
            return 0;
        }
        long s = Math.min(limit - index, n);
        index += s;
        return s;
    }

    @Override
    public int available() {
        if (limit < 0) {
            return Integer.MAX_VALUE;
        }
        return (int) Math.max(0, limit - index);
    }

    @Override
    public boolean markSupported() {
        return true;
    }

    @Override
    public synchronized void mark(int readAheadLimit) {
        mark = index;
    }

    @Override
    public synchronized void reset() {
        index = mark;
    }

    /**
     * Closes this input stream. This will reset the input stream to its default settings.
     */
    @Override
    public void close() {
        index = 0;
        mark = 0;
    }

    private byte nextByte() {
        return (byte) byteGenerator.applyAsInt(random);
    }

    /**
     * Returns a builder for random input streams that can return all possible bytes.
     *
     * @return A builder for random input streams that can return all possible bytes.
     */
    public static Builder usingAllBytes() {
        return usingGenerator(r -> r.nextInt(Byte.MAX_VALUE + 1));
    }

    /**
     * Returns a builder for random input streams that can return bytes from a specific range.
     * For the start and end of the range, only the 8 low-order bits are used; the 24 high-order bits are ignored.
     * It's therefore advised to use unsigned values between {@code 0} and {@code 255} or {@code 0xFF}.
     *
     * @param start The start of the range, inclusive.
     * @param end The end of the range, inclusive.
     * @return A builder for random input streams that can return bytes from the given range.
     * @throws IllegalArgumentException If {@code start} is larger than {@code end}.
     */
    public static Builder usingRange(int start, int end) {
        int s = start & 0xFF;
        int e = end & 0xFF;
        if (s > e) {
            throw new IllegalArgumentException(s + " > " + e); //$NON-NLS-1$
        }
        // both from and to are inclusive, so add 1
        return usingGenerator(r -> s + r.nextInt(e - s + 1));
    }

    /**
     * Returns a builder for random input streams that can return bytes from a specific range.
     * This method is equal to {@link #usingRange(int, int) usingRange(start, 0xFF)}.
     *
     * @param start The start of the range, inclusive.
     * @return A builder for random input streams that can return bytes from the given range.
     */
    public static Builder usingRangeFrom(int start) {
        return usingRange(start, 0xFF);
    }

    /**
     * Returns a builder for random input streams that can return bytes from a specific range.
     * This method is equal to {@link #usingRange(int, int) usingRange(0, end)}.
     *
     * @param end The end of the range, inclusive.
     * @return A builder for random input streams that can return bytes from the given range.
     */
    public static Builder usingRangeUntil(int end) {
        return usingRange(0, end);
    }

    /**
     * Returns a builder for random input streams that uses a custom generator for bytes.
     * For each generated value, only the 8 low-order bits are used; the 24 high-order bits are ignored.
     *
     * @param generator The generator to use.
     * @return A builder for random input streams that uses the given custom generator.
     * @throws NullPointerException If the given custom generator is {@code null}.
     */
    public static Builder usingGenerator(ToIntFunction generator) {
        Objects.requireNonNull(generator);
        return new Builder(generator);
    }

    /**
     * A builder for {@link RandomInputStream random input streams}.
     *
     * @author Rob Spoor
     */
    public static final class Builder {

        private final ToIntFunction byteGenerator;

        private Random random;

        private long limit = -1;
        private ToLongFunction limitGenerator = null;

        private Builder(ToIntFunction byteGenerator) {
            this.byteGenerator = byteGenerator;
        }

        /**
         * Specifies the random to use. By default a new {@link SecureRandom} will be created.
         *
         * @param random The random to use.
         * @return This object.
         * @throws NullPointerException If the given random is {@code null}.
         */
        public Builder withRandom(Random random) {
            this.random = Objects.requireNonNull(random);
            return this;
        }

        /**
         * Specifies the maximum number of bytes to return while reading. By default there is no limit.
         * Use a negative value to specify no limit.
         * 

* Calling this method will discard any changes made by {@link #withRandomLimit(int, int)}. * * @param limit The limit to set. * @return This object. */ public Builder withLimit(long limit) { this.limit = Math.max(limit, -1); this.limitGenerator = null; return this; } /** * Specifies that a random number should be used for the maximum number of bytes to return while reading. By default there is no limit. *

* Calling this method will cause any changes made by {@link #withLimit(long)} to be ignored. * * @param min The minimum allowed limit, inclusive. * @param max The maximum allowed limit, exclusive. * @return This object. * @throws IllegalArgumentException If the minimum is negative, or the maximum is not larger than the minimum. */ public Builder withRandomLimit(int min, int max) { if (min < 0) { throw new IllegalArgumentException(min + " < 0"); //$NON-NLS-1$ } if (max <= min) { throw new IllegalArgumentException(max + " <= " + min); //$NON-NLS-1$ } this.limitGenerator = r -> min + r.nextInt(max - min); return this; } /** * This method allows the application of a function to this builder object. *

* Any exception thrown by the function will be propagated to the caller. * * @param The type of the result of the function. * @param f The function to apply. * @return The result of applying the function to this builder object. */ public R transform(Function f) { return f.apply(this); } /** * Creates a new {@link RandomInputStream random input stream} with the settings from this builder. * * @return The created random input stream. */ public RandomInputStream build() { return new RandomInputStream(this); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy