    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 *      http://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 functional, light weight {@link InputStream} that emulates a stream of a specified size.
    027 * <p>
    028 * This implementation provides a light weight 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 *      public TestInputStream(int size) {
    042 *          super(size);
    043 *      }
    044 *      protected int processByte() {
    045 *          return ... // return required value here
    046 *      }
    047 *      protected void processBytes(byte[] bytes, int offset, int length) {
    048 *          for (int i = offset; i &lt; length; i++) {
    049 *              bytes[i] = ... // set array value here
    050 *          }
    051 *      }
    052 *  }
    053 * </pre>
    054 *
    055 * @since 1.3
    056 */
    057public class NullInputStream extends InputStream {
    058
    059    /**
    060     * The singleton instance.
    061     *
    062     * @since 2.12.0
    063     */
    064    public static final NullInputStream INSTANCE = new NullInputStream();
    065
    066    private final long size;
    067    private long position;
    068    private long mark = -1;
    069    private long readLimit;
    070    private boolean eof;
    071    private final boolean throwEofException;
    072    private final boolean markSupported;
    073
    074    /**
    075     * Constructs an {@link InputStream} that emulates a size 0 stream which supports marking and does not throw EOFException.
    076     *
    077     * @since 2.7
    078     */
    079    public NullInputStream() {
    080        this(0, true, false);
    081    }
    082
    083    /**
    084     * Constructs an {@link InputStream} that emulates a specified size which supports marking and does not throw EOFException.
    085     *
    086     * @param size The size of the input stream to emulate.
    087     */
    088    public NullInputStream(final long size) {
    089        this(size, true, false);
    090    }
    091
    092    /**
    093     * Constructs an {@link InputStream} that emulates a specified size with option settings.
    094     *
    095     * @param size              The size of the input stream to emulate.
    096     * @param markSupported     Whether this instance will support the {@code mark()} functionality.
    097     * @param throwEofException Whether this implementation will throw an {@link EOFException} or return -1 when the end of file is reached.
    098     */
    099    public NullInputStream(final long size, final boolean markSupported, final boolean throwEofException) {
    100        this.size = size;
    101        this.markSupported = markSupported;
    102        this.throwEofException = throwEofException;
    103    }
    104
    105    /**
    106     * Returns the number of bytes that can be read.
    107     *
    108     * @return The number of bytes that can be read.
    109     */
    110    @Override
    111    public int available() {
    112        final long avail = size - position;
    113        if (avail <= 0) {
    114            return 0;
    115        }
    116        if (avail > Integer.MAX_VALUE) {
    117            return Integer.MAX_VALUE;
    118        }
    119        return (int) avail;
    120    }
    121
    122    /**
    123     * Throws {@link EOFException} if {@code throwEofException} is enabled.
    124     *
    125     * @param message The {@link EOFException} message.
    126     * @throws EOFException Thrown if {@code throwEofException} is enabled.
    127     */
    128    private void checkThrowEof(final String message) throws EOFException {
    129        if (throwEofException) {
    130            throw new EOFException(message);
    131        }
    132    }
    133
    134    /**
    135     * Closes this input stream - resets the internal state to the initial values.
    136     *
    137     * @throws IOException If an error occurs.
    138     */
    139    @Override
    140    public void close() throws IOException {
    141        eof = false;
    142        position = 0;
    143        mark = -1;
    144    }
    145
    146    /**
    147     * Gets the current position.
    148     *
    149     * @return the current position.
    150     */
    151    public long getPosition() {
    152        return position;
    153    }
    154
    155    /**
    156     * Gets the size this {@link InputStream} emulates.
    157     *
    158     * @return The size of the input stream to emulate.
    159     */
    160    public long getSize() {
    161        return size;
    162    }
    163
    164    /**
    165     * Handles End of File.
    166     *
    167     * @return {@code -1} if {@code throwEofException} is set to {@code false}
    168     * @throws EOFException if {@code throwEofException} is set to {@code true}.
    169     */
    170    private int handleEof() throws EOFException {
    171        eof = true;
    172        checkThrowEof("handleEof()");
    173        return EOF;
    174    }
    175
    176    /**
    177     * Marks the current position.
    178     *
    179     * @param readLimit The number of bytes before this marked position is invalid.
    180     * @throws UnsupportedOperationException if mark is not supported.
    181     */
    182    @Override
    183    public synchronized void mark(final int readLimit) {
    184        if (!markSupported) {
    185            throw UnsupportedOperationExceptions.mark();
    186        }
    187        mark = position;
    188        this.readLimit = readLimit;
    189    }
    190
    191    /**
    192     * Tests whether <i>mark</i> is supported.
    193     *
    194     * @return Whether <i>mark</i> is supported or not.
    195     */
    196    @Override
    197    public boolean markSupported() {
    198        return markSupported;
    199    }
    200
    201    /**
    202     * Returns a byte value for the {@code read()} method.
    203     * <p>
    204     * This implementation returns zero.
    205     *
    206     * @return This implementation always returns zero.
    207     */
    208    protected int processByte() {
    209        // do nothing - overridable by subclass
    210        return 0;
    211    }
    212
    213    /**
    214     * Processes the bytes for the {@code read(byte[], offset, length)} method.
    215     * <p>
    216     * This implementation leaves the byte array unchanged.
    217     * </p>
    218     *
    219     * @param bytes  The byte array
    220     * @param offset The offset to start at.
    221     * @param length The number of bytes.
    222     */
    223    protected void processBytes(final byte[] bytes, final int offset, final int length) {
    224        // do nothing - overridable by subclass
    225    }
    226
    227    /**
    228     * Reads a byte.
    229     *
    230     * @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
    231     *         {@code false}.
    232     * @throws EOFException if the end of file is reached and {@code throwEofException} is set to {@code true}.
    233     * @throws IOException  if trying to read past the end of file.
    234     */
    235    @Override
    236    public int read() throws IOException {
    237        if (eof) {
    238            checkThrowEof("read()");
    239            return EOF;
    240        }
    241        if (position == size) {
    242            return handleEof();
    243        }
    244        position++;
    245        return processByte();
    246    }
    247
    248    /**
    249     * Reads some bytes into the specified array.
    250     *
    251     * @param bytes The byte array to read into
    252     * @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}.
    253     * @throws EOFException if the end of file is reached and {@code throwEofException} is set to {@code true}.
    254     * @throws IOException  if trying to read past the end of file.
    255     */
    256    @Override
    257    public int read(final byte[] bytes) throws IOException {
    258        return read(bytes, 0, bytes.length);
    259    }
    260
    261    /**
    262     * Reads the specified number bytes into an array.
    263     *
    264     * @param bytes  The byte array to read into.
    265     * @param offset The offset to start reading bytes into.
    266     * @param length The number of bytes to read.
    267     * @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}.
    268     * @throws EOFException if the end of file is reached and {@code throwEofException} is set to {@code true}.
    269     * @throws IOException  if trying to read past the end of file.
    270     */
    271    @Override
    272    public int read(final byte[] bytes, final int offset, final int length) throws IOException {
    273        if (eof) {
    274            checkThrowEof("read(byte[], int, int)");
    275            return EOF;
    276        }
    277        if (position == size) {
    278            return handleEof();
    279        }
    280        position += length;
    281        int returnLength = length;
    282        if (position > size) {
    283            returnLength = length - (int) (position - size);
    284            position = size;
    285        }
    286        processBytes(bytes, offset, returnLength);
    287        return returnLength;
    288    }
    289
    290    /**
    291     * Resets the stream to the point when mark was last called.
    292     *
    293     * @throws UnsupportedOperationException if mark is not supported.
    294     * @throws IOException                   If no position has been marked or the read limit has been exceeded since the last position was marked.
    295     */
    296    @Override
    297    public synchronized void reset() throws IOException {
    298        if (!markSupported) {
    299            throw UnsupportedOperationExceptions.reset();
    300        }
    301        if (mark < 0) {
    302            throw new IOException("No position has been marked");
    303        }
    304        if (position > mark + readLimit) {
    305            throw new IOException("Marked position [" + mark + "] is no longer valid - passed the read limit [" + readLimit + "]");
    306        }
    307        position = mark;
    308        eof = false;
    309    }
    310
    311    /**
    312     * Skips a specified number of bytes.
    313     *
    314     * @param numberOfBytes The number of bytes to skip.
    315     * @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}.
    316     * @throws EOFException if the end of file is reached and {@code throwEofException} is set to {@code true}.
    317     * @throws IOException  if trying to read past the end of file.
    318     */
    319    @Override
    320    public long skip(final long numberOfBytes) throws IOException {
    321        if (eof) {
    322            checkThrowEof("skip(long)");
    323            return EOF;
    324        }
    325        if (position == size) {
    326            return handleEof();
    327        }
    328        position += numberOfBytes;
    329        long returnLength = numberOfBytes;
    330        if (position > size) {
    331            returnLength = numberOfBytes - (position - size);
    332            position = size;
    333        }
    334        return returnLength;
    335    }
    336
    337}