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}