    001/*
    002 * Licensed to the Apache Software Foundation (ASF) under one or more
    003 * contributor license agreements.  See the NOTICE file distributed with
    004 * this work for additional information regarding copyright ownership.
    005 * The ASF licenses this file to You under the Apache License, Version 2.0
    006 * (the "License"); you may not use this file except in compliance with
    007 * the License.  You may obtain a copy of the License at
    008 *
    009 *      https://www.apache.org/licenses/LICENSE-2.0
    010 *
    011 * Unless required by applicable law or agreed to in writing, software
    012 * distributed under the License is distributed on an "AS IS" BASIS,
    013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014 * See the License for the specific language governing permissions and
    015 * limitations under the License.
    016 */
    017package org.apache.commons.io.input;
    018
    019import static org.apache.commons.io.IOUtils.EOF;
    020
    021import java.io.EOFException;
    022import java.io.IOException;
    023import java.io.InputStream;
    024
    025/**
    026 * A lightweight {@link InputStream} that emulates a stream of a specified size.
    027 * <p>
    028 * This implementation provides a lightweight object for testing with an {@link InputStream} where the contents don't matter.
    029 * </p>
    030 * <p>
    031 * One use case would be for testing the handling of large {@link InputStream} as it can emulate that scenario without the overhead of actually processing large
    032 * numbers of bytes - significantly speeding up test execution times.
    033 * </p>
    034 * <p>
    035 * This implementation returns zero from the method that reads a byte and leaves the array unchanged in the read methods that are passed a byte array. If
    036 * alternative data is required the {@code processByte()} and {@code processBytes()} methods can be implemented to generate data, for example:
    037 * </p>
    038 *
    039 * <pre>
    040 *  public class TestInputStream extends NullInputStream {
    041 *
    042 *      public TestInputStream(int size) {
    043 *          super(size);
    044 *      }
    045 *
    046 *      protected int processByte() {
    047 *          return ... // return required value here
    048 *      }
    049 *
    050 *      protected void processBytes(byte[] bytes, int offset, int length) {
    051 *          for (int i = offset; i &lt; length; i++) {
    052 *              bytes[i] = ... // set array value here
    053 *          }
    054 *      }
    055 *  }
    056 * </pre>
    057 * <p>
    058 * This class is not thread-safe.
    059 * </p>
    060 *
    061 * @since 1.3
    062 */
    063public class NullInputStream extends AbstractInputStream {
    064
    065    /**
    066     * The singleton instance.
    067     *
    068     * <p>
    069     * Since instances hold state, call {@link #init()} to reuse.
    070     * </p>
    071     *
    072     * @since 2.12.0
    073     * @deprecated Not reusable without calling {@link #init()} to reset state.
    074     */
    075    @Deprecated
    076    public static final NullInputStream INSTANCE = new NullInputStream();
    077
    078    private final long size;
    079    private long position;
    080    private long mark = -1;
    081    private long readLimit;
    082    private final boolean throwEofException;
    083    private final boolean markSupported;
    084
    085    /**
    086     * Constructs an {@link InputStream} that emulates a size 0 stream which supports marking and does not throw EOFException.
    087     * <p>
    088     * This is an "empty" input stream.
    089     * </p>
    090     *
    091     * @since 2.7
    092     */
    093    public NullInputStream() {
    094        this(0, true, false);
    095    }
    096
    097    /**
    098     * Constructs an {@link InputStream} that emulates a specified size which supports marking and does not throw EOFException.
    099     *
    100     * @param size The size of the input stream to emulate.
    101     */
    102    public NullInputStream(final long size) {
    103        this(size, true, false);
    104    }
    105
    106    /**
    107     * Constructs an {@link InputStream} that emulates a specified size with option settings.
    108     *
    109     * @param size              The size of the input stream to emulate.
    110     * @param markSupported     Whether this instance will support the {@code mark()} functionality.
    111     * @param throwEofException Whether this implementation will throw an {@link EOFException} or return -1 when the end of file is reached.
    112     */
    113    public NullInputStream(final long size, final boolean markSupported, final boolean throwEofException) {
    114        this.size = size;
    115        this.markSupported = markSupported;
    116        this.throwEofException = throwEofException;
    117    }
    118
    119    @Override
    120    public int available() {
    121        if (isClosed()) {
    122            return 0;
    123        }
    124        final long avail = size - position;
    125        if (avail <= 0) {
    126            return 0;
    127        }
    128        if (avail > Integer.MAX_VALUE) {
    129            return Integer.MAX_VALUE;
    130        }
    131        return (int) avail;
    132    }
    133
    134    /**
    135     * Throws {@link EOFException} if {@code throwEofException} is enabled.
    136     *
    137     * @param message The {@link EOFException} message.
    138     * @throws EOFException Thrown if {@code throwEofException} is enabled.
    139     */
    140    private void checkThrowEof(final String message) throws EOFException {
    141        if (throwEofException) {
    142            throw new EOFException(message);
    143        }
    144    }
    145
    146    /**
    147     * Closes this input stream.
    148     *
    149     * @throws IOException If an error occurs.
    150     */
    151    @Override
    152    public void close() throws IOException {
    153        super.close();
    154        mark = -1;
    155    }
    156
    157    /**
    158     * Gets the current position.
    159     *
    160     * @return the current position.
    161     */
    162    public long getPosition() {
    163        return position;
    164    }
    165
    166    /**
    167     * Gets the size this {@link InputStream} emulates.
    168     *
    169     * @return The size of the input stream to emulate.
    170     */
    171    public long getSize() {
    172        return size;
    173    }
    174
    175    /**
    176     * Handles End of File.
    177     *
    178     * @return {@code -1} if {@code throwEofException} is set to {@code false}
    179     * @throws IOException if {@code throwEofException} is set to {@code true}.
    180     */
    181    private int handleEof() throws IOException {
    182        checkThrowEof("handleEof()");
    183        return EOF;
    184    }
    185
    186    /**
    187     * Initializes or re-initializes this instance for reuse.
    188     *
    189     * @return this instance.
    190     * @since 2.17.0
    191     */
    192    public NullInputStream init() {
    193        setClosed(false);
    194        position = 0;
    195        mark = -1;
    196        readLimit = 0;
    197        return this;
    198    }
    199
    200    /**
    201     * Marks the current position.
    202     *
    203     * @param readLimit The number of bytes before this marked position is invalid.
    204     * @throws UnsupportedOperationException if mark is not supported.
    205     */
    206    @Override
    207    public synchronized void mark(final int readLimit) {
    208        if (!markSupported) {
    209            throw UnsupportedOperationExceptions.mark();
    210        }
    211        mark = position;
    212        this.readLimit = readLimit;
    213    }
    214
    215    /**
    216     * Tests whether <em>mark</em> is supported.
    217     *
    218     * @return Whether <em>mark</em> is supported or not.
    219     */
    220    @Override
    221    public boolean markSupported() {
    222        return markSupported;
    223    }
    224
    225    /**
    226     * Returns a byte value for the {@code read()} method.
    227     * <p>
    228     * This implementation returns zero.
    229     * </p>
    230     *
    231     * @return This implementation always returns zero.
    232     */
    233    protected int processByte() {
    234        // do nothing - overridable by subclass
    235        return 0;
    236    }
    237
    238    /**
    239     * Processes the bytes for the {@code read(byte[], offset, length)} method.
    240     * <p>
    241     * This implementation leaves the byte array unchanged.
    242     * </p>
    243     *
    244     * @param bytes  The byte array
    245     * @param offset The offset to start at.
    246     * @param length The number of bytes.
    247     */
    248    protected void processBytes(final byte[] bytes, final int offset, final int length) {
    249        // do nothing - overridable by subclass
    250    }
    251
    252    /**
    253     * Reads a byte.
    254     *
    255     * @return Either The byte value returned by {@code processByte()} or {@code -1} if the end of file has been reached and {@code throwEofException} is set to
    256     *         {@code false}.
    257     * @throws EOFException if the end of file is reached and {@code throwEofException} is set to {@code true}.
    258     * @throws IOException  if trying to read past the end of file.
    259     */
    260    @Override
    261    public int read() throws IOException {
    262        checkOpen();
    263        if (position == size) {
    264            return handleEof();
    265        }
    266        position++;
    267        return processByte();
    268    }
    269
    270    /**
    271     * Reads some bytes into the specified array.
    272     *
    273     * @param bytes The byte array to read into
    274     * @return The number of bytes read or {@code -1} if the end of file has been reached and {@code throwEofException} is set to {@code false}.
    275     * @throws EOFException if the end of file is reached and {@code throwEofException} is set to {@code true}.
    276     * @throws IOException  if trying to read past the end of file.
    277     */
    278    @Override
    279    public int read(final byte[] bytes) throws IOException {
    280        return read(bytes, 0, bytes.length);
    281    }
    282
    283    /**
    284     * Reads the specified number bytes into an array.
    285     *
    286     * @param bytes  The byte array to read into.
    287     * @param offset The offset to start reading bytes into.
    288     * @param length The number of bytes to read.
    289     * @return The number of bytes read or {@code -1} if the end of file has been reached and {@code throwEofException} is set to {@code false}.
    290     * @throws EOFException if the end of file is reached and {@code throwEofException} is set to {@code true}.
    291     * @throws IOException  if trying to read past the end of file.
    292     */
    293    @Override
    294    public int read(final byte[] bytes, final int offset, final int length) throws IOException {
    295        if (bytes.length == 0 || length == 0) {
    296            return 0;
    297        }
    298        checkOpen();
    299        if (position == size) {
    300            return handleEof();
    301        }
    302        position += length;
    303        int returnLength = length;
    304        if (position > size) {
    305            returnLength = length - (int) (position - size);
    306            position = size;
    307        }
    308        processBytes(bytes, offset, returnLength);
    309        return returnLength;
    310    }
    311
    312    /**
    313     * Resets the stream to the point when mark was last called.
    314     *
    315     * @throws UnsupportedOperationException if mark is not supported.
    316     * @throws IOException                   If no position has been marked or the read limit has been exceeded since the last position was marked.
    317     */
    318    @Override
    319    public synchronized void reset() throws IOException {
    320        if (!markSupported) {
    321            throw UnsupportedOperationExceptions.reset();
    322        }
    323        if (mark < 0) {
    324            throw new IOException("No position has been marked");
    325        }
    326        if (position > mark + readLimit) {
    327            throw new IOException("Marked position [" + mark + "] is no longer valid - passed the read limit [" + readLimit + "]");
    328        }
    329        position = mark;
    330        setClosed(false);
    331    }
    332
    333    /**
    334     * Skips a specified number of bytes.
    335     *
    336     * @param numberOfBytes The number of bytes to skip.
    337     * @return The number of bytes skipped or {@code -1} if the end of file has been reached and {@code throwEofException} is set to {@code false}.
    338     * @throws EOFException if the end of file is reached and {@code throwEofException} is set to {@code true}.
    339     * @throws IOException  if trying to read past the end of file.
    340     */
    341    @Override
    342    public long skip(final long numberOfBytes) throws IOException {
    343        if (isClosed()) {
    344            checkThrowEof("skip(long)");
    345            return EOF;
    346        }
    347        if (position == size) {
    348            return handleEof();
    349        }
    350        position += numberOfBytes;
    351        long returnLength = numberOfBytes;
    352        if (position > size) {
    353            returnLength = numberOfBytes - (position - size);
    354            position = size;
    355        }
    356        return returnLength;
    357    }
    358
    359}