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.InputStream;
24  
25  import org.apache.commons.io.IOUtils;
26  
27  /**
28   * A lightweight {@link InputStream} that emulates a stream of a specified size.
29   * <p>
30   * This implementation provides a lightweight object for testing with an {@link InputStream} where the contents don't matter.
31   * </p>
32   * <p>
33   * 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
34   * numbers of bytes - significantly speeding up test execution times.
35   * </p>
36   * <p>
37   * 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
38   * alternative data is required the {@code processByte()} and {@code processBytes()} methods can be implemented to generate data, for example:
39   * </p>
40   *
41   * <pre>
42   *  public class TestInputStream extends NullInputStream {
43   *
44   *      public TestInputStream(int size) {
45   *          super(size);
46   *      }
47   *
48   *      protected int processByte() {
49   *          return ... // return required value here
50   *      }
51   *
52   *      protected void processBytes(byte[] bytes, int offset, int length) {
53   *          for (int i = offset; i &lt; length; i++) {
54   *              bytes[i] = ... // set array value here
55   *          }
56   *      }
57   *  }
58   * </pre>
59   * <p>
60   * This class is not thread-safe.
61   * </p>
62   *
63   * @since 1.3
64   */
65  public class NullInputStream extends AbstractInputStream {
66  
67      /**
68       * The singleton instance.
69       *
70       * <p>
71       * Since instances hold state, call {@link #init()} to reuse.
72       * </p>
73       *
74       * @since 2.12.0
75       * @deprecated Not reusable without calling {@link #init()} to reset state.
76       */
77      @Deprecated
78      public static final NullInputStream INSTANCE = new NullInputStream();
79  
80      private final long size;
81      private long position;
82      private long mark = -1;
83      private long readLimit;
84      private final boolean throwEofException;
85      private final boolean markSupported;
86  
87      /**
88       * Constructs an {@link InputStream} that emulates a size 0 stream which supports marking and does not throw EOFException.
89       * <p>
90       * This is an "empty" input stream.
91       * </p>
92       *
93       * @since 2.7
94       */
95      public NullInputStream() {
96          this(0, true, false);
97      }
98  
99      /**
100      * Constructs an {@link InputStream} that emulates a specified size which supports marking and does not throw EOFException.
101      *
102      * @param size The size of the input stream to emulate.
103      */
104     public NullInputStream(final long size) {
105         this(size, true, false);
106     }
107 
108     /**
109      * Constructs an {@link InputStream} that emulates a specified size with option settings.
110      *
111      * @param size              The size of the input stream to emulate.
112      * @param markSupported     Whether this instance will support the {@code mark()} functionality.
113      * @param throwEofException Whether this implementation will throw an {@link EOFException} or return -1 when the end of file is reached.
114      */
115     public NullInputStream(final long size, final boolean markSupported, final boolean throwEofException) {
116         this.size = size;
117         this.markSupported = markSupported;
118         this.throwEofException = throwEofException;
119     }
120 
121     @Override
122     public int available() {
123         if (isClosed()) {
124             return 0;
125         }
126         final long avail = size - position;
127         if (avail <= 0) {
128             return 0;
129         }
130         if (avail > Integer.MAX_VALUE) {
131             return Integer.MAX_VALUE;
132         }
133         return (int) avail;
134     }
135 
136     /**
137      * Throws {@link EOFException} if {@code throwEofException} is enabled.
138      *
139      * @param message The {@link EOFException} message.
140      * @throws EOFException Thrown if {@code throwEofException} is enabled.
141      */
142     private void checkThrowEof(final String message) throws EOFException {
143         if (throwEofException) {
144             throw new EOFException(message);
145         }
146     }
147 
148     /**
149      * Closes this input stream.
150      *
151      * @throws IOException If an error occurs.
152      */
153     @Override
154     public void close() throws IOException {
155         super.close();
156         mark = -1;
157     }
158 
159     /**
160      * Gets the current position.
161      *
162      * @return the current position.
163      */
164     public long getPosition() {
165         return position;
166     }
167 
168     /**
169      * Gets the size this {@link InputStream} emulates.
170      *
171      * @return The size of the input stream to emulate.
172      */
173     public long getSize() {
174         return size;
175     }
176 
177     /**
178      * Handles End of File.
179      *
180      * @return {@code -1} if {@code throwEofException} is set to {@code false}
181      * @throws IOException if {@code throwEofException} is set to {@code true}.
182      */
183     private int handleEof() throws IOException {
184         checkThrowEof("handleEof()");
185         return EOF;
186     }
187 
188     /**
189      * Initializes or re-initializes this instance for reuse.
190      *
191      * @return {@code this} instance.
192      * @since 2.17.0
193      */
194     public NullInputStream init() {
195         setClosed(false);
196         position = 0;
197         mark = -1;
198         readLimit = 0;
199         return this;
200     }
201 
202     /**
203      * Marks the current position.
204      *
205      * @param readLimit The number of bytes before this marked position is invalid.
206      * @throws UnsupportedOperationException if mark is not supported.
207      */
208     @Override
209     public synchronized void mark(final int readLimit) {
210         if (!markSupported) {
211             throw UnsupportedOperationExceptions.mark();
212         }
213         mark = position;
214         this.readLimit = readLimit;
215     }
216 
217     /**
218      * Tests whether <em>mark</em> is supported.
219      *
220      * @return Whether <em>mark</em> is supported or not.
221      */
222     @Override
223     public boolean markSupported() {
224         return markSupported;
225     }
226 
227     /**
228      * Returns a byte value for the {@code read()} method.
229      * <p>
230      * This implementation returns zero.
231      * </p>
232      *
233      * @return This implementation always returns zero.
234      */
235     protected int processByte() {
236         // do nothing - overridable by subclass
237         return 0;
238     }
239 
240     /**
241      * Processes the bytes for the {@code read(byte[], offset, length)} method.
242      * <p>
243      * This implementation leaves the byte array unchanged.
244      * </p>
245      *
246      * @param bytes  The byte array, never {@code null}.
247      * @param offset The offset to start at, always non-negative.
248      * @param length The number of bytes to process, always non-negative and at most {@code bytes.length - offset}.
249      */
250     protected void processBytes(final byte[] bytes, final int offset, final int length) {
251         // do nothing - overridable by subclass
252     }
253 
254     /**
255      * Reads a byte.
256      *
257      * @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
258      *         {@code false}.
259      * @throws EOFException if the end of file is reached and {@code throwEofException} is set to {@code true}.
260      * @throws IOException  if trying to read past the end of file.
261      */
262     @Override
263     public int read() throws IOException {
264         checkOpen();
265         if (position == size) {
266             return handleEof();
267         }
268         position++;
269         return processByte();
270     }
271 
272     /**
273      * Reads some bytes into the specified array.
274      *
275      * @param bytes The byte array to read into
276      * @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}.
277      * @throws NullPointerException if the byte array is {@code null}.
278      * @throws EOFException if the end of file is reached and {@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 byte[] bytes) throws IOException {
283         return read(bytes, 0, bytes.length);
284     }
285 
286     /**
287      * Reads the specified number bytes into an array.
288      *
289      * @param bytes  The byte array to read into.
290      * @param offset The offset to start reading bytes into.
291      * @param length The number of bytes to read.
292      * @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}.
293      * @throws NullPointerException if the byte array is {@code null}.
294      * @throws IndexOutOfBoundsException if {@code offset} or {@code length} are negative, or if {@code offset + length} is greater than {@code bytes.length}.
295      * @throws EOFException if the end of file is reached and {@code throwEofException} is set to {@code true}.
296      * @throws IOException  if trying to read past the end of file.
297      */
298     @Override
299     public int read(final byte[] bytes, final int offset, final int length) throws IOException {
300         IOUtils.checkFromIndexSize(bytes, offset, length);
301         if (length == 0) {
302             return 0;
303         }
304         checkOpen();
305         if (position == size) {
306             return handleEof();
307         }
308         position += length;
309         int returnLength = length;
310         if (position > size) {
311             returnLength = length - (int) (position - size);
312             position = size;
313         }
314         processBytes(bytes, offset, returnLength);
315         return returnLength;
316     }
317 
318     /**
319      * Resets the stream to the point when mark was last called.
320      *
321      * @throws UnsupportedOperationException if mark is not supported.
322      * @throws IOException                   If no position has been marked or the read limit has been exceeded since the last position was marked.
323      */
324     @Override
325     public synchronized void reset() throws IOException {
326         if (!markSupported) {
327             throw UnsupportedOperationExceptions.reset();
328         }
329         if (mark < 0) {
330             throw new IOException("No position has been marked");
331         }
332         if (position > mark + readLimit) {
333             throw new IOException("Marked position [" + mark + "] is no longer valid - passed the read limit [" + readLimit + "]");
334         }
335         position = mark;
336         setClosed(false);
337     }
338 
339     /**
340      * Skips a specified number of bytes.
341      *
342      * @param numberOfBytes The number of bytes to skip.
343      * @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}.
344      * @throws EOFException if the end of file is reached and {@code throwEofException} is set to {@code true}.
345      * @throws IOException  if trying to read past the end of file.
346      */
347     @Override
348     public long skip(final long numberOfBytes) throws IOException {
349         if (isClosed()) {
350             checkThrowEof("skip(long)");
351             return EOF;
352         }
353         if (position == size) {
354             return handleEof();
355         }
356         position += numberOfBytes;
357         long returnLength = numberOfBytes;
358         if (position > size) {
359             returnLength = numberOfBytes - (position - size);
360             position = size;
361         }
362         return returnLength;
363     }
364 
365 }