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