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