IO-94 - New MockReader implementation
git-svn-id: https://svn.apache.org/repos/asf/jakarta/commons/proper/io/trunk@454248 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/src/java/org/apache/commons/io/input/MockReader.java b/src/java/org/apache/commons/io/input/MockReader.java
new file mode 100644
index 0000000..1085c3a
--- /dev/null
+++ b/src/java/org/apache/commons/io/input/MockReader.java
@@ -0,0 +1,293 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.io.input;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.Reader;
+
+/**
+ * A mock {@link Reader} for testing purposes.
+ * <p>
+ * This implementation provides a light weight mock
+ * object for testing with a {@link Reader}
+ * where the contents don't matter.
+ * <p>
+ * One use case would be for testing the handling of
+ * large {@link Reader} as it can emulate that
+ * scenario without the overhead of actually processing
+ * large numbers of characters - significantly speeding up
+ * test execution times.
+ * <p>
+ * Alternatively, if some kind of data is required as part
+ * of a test the <code>processChar()</code> and
+ * <code>processChars()</code> methods can be implemented to generate
+ * test data.
+ *
+ *
+ * @since Commons IO 1.3
+ * @version $Revision$
+ */
+public class MockReader extends Reader {
+
+ private long size;
+ private long position;
+ private long mark = -1;
+ private long readlimit;
+ private boolean eof;
+ private boolean throwEofException;
+ private boolean markSupported;
+
+ /**
+ * Create a mock {@link Reader} of the specified size.
+ *
+ * @param size The size of the mock Reader.
+ */
+ public MockReader(long size) {
+ this(size, true, false);
+ }
+
+ /**
+ * Create a mock {@link Reader} of the specified
+ * size and option settings.
+ *
+ * @param size The size of the mock Reader.
+ * @param markSupported Whether this instance will support
+ * the <code>mark()</code> functionality.
+ * @param throwEofException Whether this implementation
+ * will throw an {@link EOFException} or return -1 when the
+ * end of file is reached.
+ */
+ public MockReader(long size, boolean markSupported, boolean throwEofException) {
+ this.size = size;
+ this.markSupported = markSupported;
+ this.throwEofException = throwEofException;
+ }
+
+ /**
+ * Return the current position.
+ *
+ * @return the current position.
+ */
+ public long getPosition() {
+ return position;
+ }
+
+ /**
+ * Return the size of this Mock {@link Reader}
+ *
+ * @return the size of the mock Reader.
+ */
+ public long getSize() {
+ return size;
+ }
+
+ /**
+ * Close this Reader - resets the internal state to
+ * the initial values.
+ *
+ * @throws IOException If an error occurs.
+ */
+ public void close() throws IOException {
+ eof = false;
+ position = 0;
+ mark = -1;
+ }
+
+ /**
+ * Mark the current position.
+ *
+ * @param readlimit The number of characters before this marked position
+ * is invalid.
+ * @throws UnsupportedOperationException if mark is not supported.
+ */
+ public synchronized void mark(int readlimit) {
+ if (!markSupported) {
+ throw new UnsupportedOperationException("Mark not supported");
+ }
+ mark = position;
+ this.readlimit = readlimit;
+ }
+
+ /**
+ * Indicates whether <i>mark</i> is supported.
+ *
+ * @return Whether <i>mark</i> is supported or not.
+ */
+ public boolean markSupported() {
+ return markSupported;
+ }
+
+ /**
+ * Read a character.
+ *
+ * @return Either The character value returned by <code>processChar()</code>
+ * or <code>-1</code> if the end of file has been reached and
+ * <code>throwEofException</code> is set to <code>false</code>.
+ * @throws EOFException if the end of file is reached and
+ * <code>throwEofException</code> is set to <code>true</code>.
+ * @throws IOException if trying to read past the end of file.
+ */
+ public int read() throws IOException {
+ if (eof) {
+ throw new IOException("Read after end of file");
+ }
+ if (position == size) {
+ return doEndOfFile();
+ }
+ position++;
+ return processChar();
+ }
+
+ /**
+ * Read some characters into the specified array.
+ *
+ * @param chars The character array to read into
+ * @return The number of characters read or <code>-1</code>
+ * if the end of file has been reached and
+ * <code>throwEofException</code> is set to <code>false</code>.
+ * @throws EOFException if the end of file is reached and
+ * <code>throwEofException</code> is set to <code>true</code>.
+ * @throws IOException if trying to read past the end of file.
+ */
+ public int read(char[] chars) throws IOException {
+ return read(chars, 0, chars.length);
+ }
+
+ /**
+ * Read the specified number characters into an array.
+ *
+ * @param chars The character array to read into.
+ * @param offset The offset to start reading characters into.
+ * @param length The number of characters to read.
+ * @return The number of characters read or <code>-1</code>
+ * if the end of file has been reached and
+ * <code>throwEofException</code> is set to <code>false</code>.
+ * @throws EOFException if the end of file is reached and
+ * <code>throwEofException</code> is set to <code>true</code>.
+ * @throws IOException if trying to read past the end of file.
+ */
+ public int read(char[] chars, int offset, int length) throws IOException {
+ if (eof) {
+ throw new IOException("Read after end of file");
+ }
+ if (position == size) {
+ return doEndOfFile();
+ }
+ position += length;
+ int returnLength = length;
+ if (position > size) {
+ returnLength = length - (int)(position - size);
+ position = size;
+ }
+ processChars(chars, offset, returnLength);
+ return returnLength;
+ }
+
+ /**
+ * Reset the stream to the point when mark was last called.
+ *
+ * @throws UnsupportedOperationException if mark is not supported.
+ * @throws IOException If no position has been marked
+ * or the read limit has been exceed since the last position was
+ * marked.
+ */
+ public synchronized void reset() throws IOException {
+ if (!markSupported) {
+ throw new UnsupportedOperationException("Mark not supported");
+ }
+ if (mark < 0) {
+ throw new IOException("No position has been marked");
+ }
+ if (position > (mark + readlimit)) {
+ throw new IOException("Marked position [" + mark +
+ "] is no longer valid - passed the read limit [" +
+ readlimit + "]");
+ }
+ position = mark;
+ eof = false;
+ }
+
+ /**
+ * Skip a specified number of characters.
+ *
+ * @param numberOfChars The number of characters to skip.
+ * @return The number of characters skipped or <code>-1</code>
+ * if the end of file has been reached and
+ * <code>throwEofException</code> is set to <code>false</code>.
+ * @throws EOFException if the end of file is reached and
+ * <code>throwEofException</code> is set to <code>true</code>.
+ * @throws IOException if trying to read past the end of file.
+ */
+ public long skip(long numberOfChars) throws IOException {
+ if (eof) {
+ throw new IOException("Skip after end of file");
+ }
+ if (position == size) {
+ return doEndOfFile();
+ }
+ position += numberOfChars;
+ long returnLength = numberOfChars;
+ if (position > size) {
+ returnLength = numberOfChars - (position - size);
+ position = size;
+ }
+ return returnLength;
+ }
+
+ /**
+ * Return a character value for the <code>read()</code> method.
+ * <p>
+ * <strong>N.B.</strong> This implementation returns
+ * zero.
+ *
+ * @return This implementation always returns zero.
+ */
+ protected int processChar() {
+ return 0;
+ }
+
+ /**
+ * Process the characters for the <code>read(char[], offset, length)</code>
+ * method.
+ * <p>
+ * <strong>N.B.</strong> This implementation leaves the character
+ * array unchanged.
+ *
+ * @param chars The character array
+ * @param offset The offset to start at.
+ * @param length The number of characters.
+ */
+ protected void processChars(char[] chars, int offset, int length) {
+ }
+
+ /**
+ * Handle End of File.
+ *
+ * @return <code>-1</code> if <code>throwEofException</code> is
+ * set to <code>false</code>
+ * @throws EOFException if <code>throwEofException</code> is set
+ * to <code>true</code>.
+ */
+ private int doEndOfFile() throws EOFException {
+ eof = true;
+ if (throwEofException) {
+ throw new EOFException();
+ }
+ return -1;
+ }
+}
diff --git a/src/test/org/apache/commons/io/input/MockReaderTestCase.java b/src/test/org/apache/commons/io/input/MockReaderTestCase.java
new file mode 100644
index 0000000..7b2ecbd
--- /dev/null
+++ b/src/test/org/apache/commons/io/input/MockReaderTestCase.java
@@ -0,0 +1,253 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.io.input;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.Reader;
+
+import junit.framework.TestCase;
+
+/**
+ * JUnit Test Case for {@link MockReader}.
+ *
+ * @version $Id$
+ */
+public class MockReaderTestCase extends TestCase {
+
+ /** Constructor */
+ public MockReaderTestCase(String name) {
+ super(name);
+ }
+
+ /** Set up */
+ protected void setUp() throws Exception {
+ super.setUp();
+ }
+
+ /** Tear Down */
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ /**
+ * Test <code>available()</code> method.
+ */
+ public void testRead() throws Exception {
+ int size = 5;
+ TestMockReader reader = new TestMockReader(size);
+ for (int i = 0; i < size; i++) {
+ assertEquals("Check Value [" + i + "]", i, reader.read());
+ }
+
+ // Check End of File
+ assertEquals("End of File", -1, reader.read());
+
+ // Test reading after the end of file
+ try {
+ int result = reader.read();
+ fail("Should have thrown an IOException, value=[" + result + "]");
+ } catch (IOException e) {
+ assertEquals("Read after end of file", e.getMessage());
+ }
+
+ // Close - should reset
+ reader.close();
+ assertEquals("Available after close", 0, reader.getPosition());
+ }
+
+ /**
+ * Test <code>read(char[])</code> method.
+ */
+ public void testReadCharArray() throws Exception {
+ char[] chars = new char[10];
+ Reader reader = new TestMockReader(15);
+
+ // Read into array
+ int count1 = reader.read(chars);
+ assertEquals("Read 1", chars.length, count1);
+ for (int i = 0; i < count1; i++) {
+ assertEquals("Check Chars 1", i, chars[i]);
+ }
+
+ // Read into array
+ int count2 = reader.read(chars);
+ assertEquals("Read 2", 5, count2);
+ for (int i = 0; i < count2; i++) {
+ assertEquals("Check Chars 2", count1 + i, chars[i]);
+ }
+
+ // End of File
+ int count3 = reader.read(chars);
+ assertEquals("Read 3 (EOF)", -1, count3);
+
+ // Test reading after the end of file
+ try {
+ int count4 = reader.read(chars);
+ fail("Should have thrown an IOException, value=[" + count4 + "]");
+ } catch (IOException e) {
+ assertEquals("Read after end of file", e.getMessage());
+ }
+
+ // reset by closing
+ reader.close();
+
+ // Read into array using offset & length
+ int offset = 2;
+ int lth = 4;
+ int count5 = reader.read(chars, offset, lth);
+ assertEquals("Read 5", lth, count5);
+ for (int i = offset; i < lth; i++) {
+ assertEquals("Check Chars 3", i, chars[i]);
+ }
+ }
+
+ /**
+ * Test when configured to throw an EOFException at the end of file
+ * (rather than return -1).
+ */
+ public void testEOFException() throws Exception {
+ Reader reader = new TestMockReader(2, false, true);
+ assertEquals("Read 1", 0, reader.read());
+ assertEquals("Read 2", 1, reader.read());
+ try {
+ int result = reader.read();
+ fail("Should have thrown an EOFException, value=[" + result + "]");
+ } catch (EOFException e) {
+ // expected
+ }
+ }
+
+ /**
+ * Test <code>mark()</code> and <code>reset()</code> methods.
+ */
+ public void testMarkAndReset() throws Exception {
+ int position = 0;
+ int readlimit = 10;
+ Reader reader = new TestMockReader(100, true, false);
+
+ assertTrue("Mark Should be Supported", reader.markSupported());
+
+ // No Mark
+ try {
+ reader.reset();
+ fail("Read limit exceeded, expected IOException ");
+ } catch (Exception e) {
+ assertEquals("No Mark IOException message",
+ "No position has been marked",
+ e.getMessage());
+ }
+
+ for (; position < 3; position++) {
+ assertEquals("Read Before Mark [" + position +"]", position, reader.read());
+ }
+
+ // Mark
+ reader.mark(readlimit);
+
+ // Read further
+ for (int i = 0; i < 3; i++) {
+ assertEquals("Read After Mark [" + i +"]", (position + i), reader.read());
+ }
+
+ // Reset
+ reader.reset();
+
+ // Read From marked position
+ for (int i = 0; i < readlimit + 1; i++) {
+ assertEquals("Read After Reset [" + i +"]", (position + i), reader.read());
+ }
+
+ // Reset after read limit passed
+ try {
+ reader.reset();
+ fail("Read limit exceeded, expected IOException ");
+ } catch (Exception e) {
+ assertEquals("Read limit IOException message",
+ "Marked position [" + position
+ + "] is no longer valid - passed the read limit ["
+ + readlimit + "]",
+ e.getMessage());
+ }
+ }
+
+ /**
+ * Test <code>mark()</code> not supported.
+ */
+ public void testMarkNotSupported() throws Exception {
+ Reader reader = new TestMockReader(100, false, true);
+ assertFalse("Mark Should NOT be Supported", reader.markSupported());
+
+ try {
+ reader.mark(5);
+ fail("mark() should throw UnsupportedOperationException");
+ } catch (UnsupportedOperationException e) {
+ assertEquals("mark() error message", "Mark not supported", e.getMessage());
+ }
+
+ try {
+ reader.reset();
+ fail("reset() should throw UnsupportedOperationException");
+ } catch (UnsupportedOperationException e) {
+ assertEquals("reset() error message", "Mark not supported", e.getMessage());
+ }
+ }
+
+ /**
+ * Test <code>skip()</code> method.
+ */
+ public void testSkip() throws Exception {
+ int skip = 20;
+ Reader reader = new TestMockReader(10, true, false);
+ assertEquals("Read 1", 0, reader.read());
+ assertEquals("Read 2", 1, reader.read());
+ assertEquals("Skip 1", 5, reader.skip(5));
+ assertEquals("Read 3", 7, reader.read());
+ assertEquals("Skip 2", 2, reader.skip(5)); // only 2 left to skip
+ assertEquals("Skip 3 (EOF)", -1, reader.skip(5)); // End of file
+ try {
+ reader.skip(5); //
+ fail("Expected IOException for skipping after end of file");
+ } catch (Exception e) {
+ assertEquals("Skip after EOF IOException message",
+ "Skip after end of file",
+ e.getMessage());
+ }
+ }
+
+
+ // ------------- Test MockReader implementation -------------
+
+ private static final class TestMockReader extends MockReader {
+ public TestMockReader(int size) {
+ super(size);
+ }
+ public TestMockReader(int size, boolean markSupported, boolean throwEofException) {
+ super(size, markSupported, throwEofException);
+ }
+ protected int processChar() {
+ return ((int)getPosition() - 1);
+ }
+ protected void processChars(char[] chars, int offset, int length) {
+ int startPos = (int)getPosition() - length;
+ for (int i = offset; i < length; i++) {
+ chars[i] = (char)(startPos + i);
+ }
+ }
+
+ }
+}