    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.Reader;
    024
    025import org.apache.commons.io.IOUtils;
    026
    027/**
    028 * A functional, lightweight {@link Reader} that emulates
    029 * a reader of a specified size.
    030 * <p>
    031 * This implementation provides a lightweight
    032 * object for testing with an {@link Reader}
    033 * where the contents don't matter.
    034 * </p>
    035 * <p>
    036 * One use case would be for testing the handling of
    037 * large {@link Reader} as it can emulate that
    038 * scenario without the overhead of actually processing
    039 * large numbers of characters - significantly speeding up
    040 * test execution times.
    041 * </p>
    042 * <p>
    043 * This implementation returns a space from the method that
    044 * reads a character and leaves the array unchanged in the read
    045 * methods that are passed a character array.
    046 * If alternative data is required the {@code processChar()} and
    047 * {@code processChars()} methods can be implemented to generate
    048 * data, for example:
    049 * </p>
    050 *
    051 * <pre>
    052 *  public class TestReader extends NullReader {
    053 *      public TestReader(int size) {
    054 *          super(size);
    055 *      }
    056 *      protected char processChar() {
    057 *          return ... // return required value here
    058 *      }
    059 *      protected void processChars(char[] chars, int offset, int length) {
    060 *          for (int i = offset; i &lt; length; i++) {
    061 *              chars[i] = ... // set array value here
    062 *          }
    063 *      }
    064 *  }
    065 * </pre>
    066 * <p>
    067 * This class is not thread-safe.
    068 * </p>
    069 *
    070 * @since 1.3
    071 */
    072public class NullReader extends Reader {
    073
    074    /**
    075     * The singleton instance.
    076     *
    077     * @since 2.12.0
    078     */
    079    public static final NullReader INSTANCE = new NullReader();
    080
    081    private final long size;
    082    private final boolean throwEofException;
    083    private final boolean markSupported;
    084
    085    private long position;
    086    private long mark = -1;
    087    private long readLimit;
    088    private boolean eof;
    089
    090    /**
    091     * Constructs a {@link Reader} that emulates a size 0 reader
    092     * which supports marking and does not throw EOFException.
    093     *
    094     * @since 2.7
    095     */
    096    public NullReader() {
    097       this(0, true, false);
    098    }
    099
    100    /**
    101     * Constructs a {@link Reader} that emulates a specified size
    102     * which supports marking and does not throw EOFException.
    103     *
    104     * @param size The size of the reader to emulate.
    105     */
    106    public NullReader(final long size) {
    107       this(size, true, false);
    108    }
    109
    110    /**
    111     * Constructs a {@link Reader} that emulates a specified
    112     * size with option settings.
    113     *
    114     * @param size The size of the reader to emulate.
    115     * @param markSupported Whether this instance will support
    116     * the {@code mark()} functionality.
    117     * @param throwEofException Whether this implementation
    118     * will throw an {@link EOFException} or return -1 when the
    119     * end of file is reached.
    120     */
    121    public NullReader(final long size, final boolean markSupported, final boolean throwEofException) {
    122       this.size = size;
    123       this.markSupported = markSupported;
    124       this.throwEofException = throwEofException;
    125    }
    126
    127    /**
    128     * Closes this Reader. Resets the internal state to
    129     * the initial values.
    130     *
    131     * @throws IOException If an error occurs.
    132     */
    133    @Override
    134    public void close() throws IOException {
    135        eof = false;
    136        position = 0;
    137        mark = -1;
    138    }
    139
    140    /**
    141     * Handles End of File.
    142     *
    143     * @return {@code -1} if {@code throwEofException} is
    144     * set to {@code false}
    145     * @throws EOFException if {@code throwEofException} is set
    146     * to {@code true}.
    147     */
    148    private int doEndOfFile() throws EOFException {
    149        eof = true;
    150        if (throwEofException) {
    151            throw new EOFException();
    152        }
    153        return EOF;
    154    }
    155
    156    /**
    157     * Gets the current position.
    158     *
    159     * @return the current position.
    160     */
    161    public long getPosition() {
    162        return position;
    163    }
    164
    165    /**
    166     * Gets the size this {@link Reader} emulates.
    167     *
    168     * @return The size of the reader to emulate.
    169     */
    170    public long getSize() {
    171        return size;
    172    }
    173
    174    /**
    175     * Marks the current position.
    176     *
    177     * @param readLimit The number of characters before this marked position
    178     * is invalid.
    179     * @throws UnsupportedOperationException if mark is not supported.
    180     */
    181    @Override
    182    public synchronized void mark(final int readLimit) {
    183        if (!markSupported) {
    184            throw UnsupportedOperationExceptions.mark();
    185        }
    186        mark = position;
    187        this.readLimit = readLimit;
    188    }
    189
    190    /**
    191     * Tests whether <em>mark</em> is supported.
    192     *
    193     * @return Whether <em>mark</em> is supported or not.
    194     */
    195    @Override
    196    public boolean markSupported() {
    197        return markSupported;
    198    }
    199
    200    /**
    201     * Returns a character value for the  {@code read()} method.
    202     * <p>
    203     * This implementation returns zero.
    204     * </p>
    205     *
    206     * @return This implementation always returns zero.
    207     */
    208    protected int processChar() {
    209        // do nothing - overridable by subclass
    210        return 0;
    211    }
    212
    213    /**
    214     * Process the characters for the {@code read(char[], offset, length)}
    215     * method.
    216     * <p>
    217     * This implementation leaves the character array unchanged.
    218     * </p>
    219     *
    220     * @param chars The character array.
    221     * @param offset The offset to start at.
    222     * @param length The number of characters.
    223     */
    224    protected void processChars(final char[] chars, final int offset, final int length) {
    225        // do nothing - overridable by subclass
    226    }
    227
    228    /**
    229     * Reads a character.
    230     *
    231     * @return Either The character value returned by {@code processChar()}
    232     * or {@code -1} if the end of file has been reached and
    233     * {@code throwEofException} is set to {@code false}.
    234     * @throws EOFException if the end of file is reached and
    235     * {@code throwEofException} is set to {@code true}.
    236     * @throws IOException if trying to read past the end of file.
    237     */
    238    @Override
    239    public int read() throws IOException {
    240        if (eof) {
    241            throw new IOException("Read after end of file");
    242        }
    243        if (position == size) {
    244            return doEndOfFile();
    245        }
    246        position++;
    247        return processChar();
    248    }
    249
    250    /**
    251     * Reads some characters into the specified array.
    252     *
    253     * @param chars The character array to read into.
    254     * @return The number of characters read or {@code -1}
    255     * if the end of file has been reached and
    256     * {@code throwEofException} is set to {@code false}.
    257     * @throws EOFException if the end of file is reached and
    258     * {@code throwEofException} is set to {@code true}.
    259     * @throws IOException if trying to read past the end of file.
    260     */
    261    @Override
    262    public int read(final char[] chars) throws IOException {
    263        return read(chars, 0, chars.length);
    264    }
    265
    266    /**
    267     * Reads the specified number characters into an array.
    268     *
    269     * @param chars The character array to read into.
    270     * @param offset The offset to start reading characters into.
    271     * @param length The number of characters to read.
    272     * @return The number of characters read or {@code -1}
    273     * if the end of file has been reached and
    274     * {@code throwEofException} is set to {@code false}.
    275     * @throws NullPointerException if the array is {@code null}.
    276     * @throws IndexOutOfBoundsException if {@code offset} or {@code length} are negative, or if {@code offset + length} is greater than {@code chars.length}.
    277     * @throws EOFException if the end of file is reached and
    278     * {@code throwEofException} is set to {@code true}.
    279     * @throws IOException if trying to read past the end of file.
    280     */
    281    @Override
    282    public int read(final char[] chars, final int offset, final int length) throws IOException {
    283        IOUtils.checkFromIndexSize(chars, offset, length);
    284        if (length == 0) {
    285            return 0;
    286        }
    287        if (eof) {
    288            throw new IOException("Read after end of file");
    289        }
    290        if (position == size) {
    291            return doEndOfFile();
    292        }
    293        position += length;
    294        int returnLength = length;
    295        if (position > size) {
    296            returnLength = length - (int) (position - size);
    297            position = size;
    298        }
    299        processChars(chars, offset, returnLength);
    300        return returnLength;
    301    }
    302
    303    /**
    304     * Resets the stream to the point when mark was last called.
    305     *
    306     * @throws UnsupportedOperationException if mark is not supported.
    307     * @throws IOException If no position has been marked
    308     * or the read limit has been exceeded since the last position was
    309     * marked.
    310     */
    311    @Override
    312    public synchronized void reset() throws IOException {
    313        if (!markSupported) {
    314            throw UnsupportedOperationExceptions.reset();
    315        }
    316        if (mark < 0) {
    317            throw new IOException("No position has been marked");
    318        }
    319        if (position > mark + readLimit) {
    320            throw new IOException("Marked position [" + mark +
    321                    "] is no longer valid - passed the read limit [" +
    322                    readLimit + "]");
    323        }
    324        position = mark;
    325        eof = false;
    326    }
    327
    328    /**
    329     * Skips a specified number of characters.
    330     *
    331     * @param numberOfChars The number of characters to skip.
    332     * @return The number of characters skipped or {@code -1}
    333     * if the end of file has been reached and
    334     * {@code throwEofException} is set to {@code false}.
    335     * @throws EOFException if the end of file is reached and
    336     * {@code throwEofException} is set to {@code true}.
    337     * @throws IOException if trying to read past the end of file.
    338     */
    339    @Override
    340    public long skip(final long numberOfChars) throws IOException {
    341        if (eof) {
    342            throw new IOException("Skip after end of file");
    343        }
    344        if (position == size) {
    345            return doEndOfFile();
    346        }
    347        position += numberOfChars;
    348        long returnLength = numberOfChars;
    349        if (position > size) {
    350            returnLength = numberOfChars - (position - size);
    351            position = size;
    352        }
    353        return returnLength;
    354    }
    355
    356}