View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      https://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.io.input;
18  
19  import static org.apache.commons.io.IOUtils.EOF;
20  
21  import java.io.EOFException;
22  import java.io.IOException;
23  import java.io.Reader;
24  
25  import org.apache.commons.io.IOUtils;
26  
27  /**
28   * A functional, lightweight {@link Reader} that emulates
29   * a reader of a specified size.
30   * <p>
31   * This implementation provides a lightweight
32   * object for testing with an {@link Reader}
33   * where the contents don't matter.
34   * </p>
35   * <p>
36   * One use case would be for testing the handling of
37   * large {@link Reader} as it can emulate that
38   * scenario without the overhead of actually processing
39   * large numbers of characters - significantly speeding up
40   * test execution times.
41   * </p>
42   * <p>
43   * This implementation returns a space from the method that
44   * reads a character and leaves the array unchanged in the read
45   * methods that are passed a character array.
46   * If alternative data is required the {@code processChar()} and
47   * {@code processChars()} methods can be implemented to generate
48   * data, for example:
49   * </p>
50   *
51   * <pre>
52   *  public class TestReader extends NullReader {
53   *      public TestReader(int size) {
54   *          super(size);
55   *      }
56   *      protected char processChar() {
57   *          return ... // return required value here
58   *      }
59   *      protected void processChars(char[] chars, int offset, int length) {
60   *          for (int i = offset; i &lt; length; i++) {
61   *              chars[i] = ... // set array value here
62   *          }
63   *      }
64   *  }
65   * </pre>
66   * <p>
67   * This class is not thread-safe.
68   * </p>
69   *
70   * @since 1.3
71   */
72  public class NullReader extends Reader {
73  
74      /**
75       * The singleton instance.
76       *
77       * @since 2.12.0
78       */
79      public static final NullReader INSTANCE = new NullReader();
80  
81      private final long size;
82      private final boolean throwEofException;
83      private final boolean markSupported;
84  
85      private long position;
86      private long mark = -1;
87      private long readLimit;
88      private boolean eof;
89  
90      /**
91       * Constructs a {@link Reader} that emulates a size 0 reader
92       * which supports marking and does not throw EOFException.
93       *
94       * @since 2.7
95       */
96      public NullReader() {
97         this(0, true, false);
98      }
99  
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 }