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