This is an automated email from the ASF dual-hosted git repository.

ggregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-io.git


The following commit(s) were added to refs/heads/master by this push:
     new c44fb5163 Add UnsynchronizedBufferedReader and UnsynchronizedReader
c44fb5163 is described below

commit c44fb516390012f9c9e9996ba9162331f8fe7ecb
Author: Gary Gregory <garydgreg...@gmail.com>
AuthorDate: Sat Sep 14 10:11:43 2024 -0400

    Add UnsynchronizedBufferedReader and UnsynchronizedReader
---
 src/changes/changes.xml                            |   2 +
 .../io/input/UnsynchronizedBufferedReader.java     | 463 +++++++++++++++++++++
 .../commons/io/input/UnsynchronizedReader.java     |  78 ++++
 .../io/input/UnsynchronizedBufferedReaderTest.java | 457 ++++++++++++++++++++
 4 files changed, 1000 insertions(+)

diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 49a1d78c3..e4338d4c1 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -54,6 +54,8 @@ The <action> type attribute can be add,update,fix,remove.
       <action dev="ggregory" type="add"                due-to="Gary 
Gregory">Add CloseShieldInputStream.systemIn(InputStream).</action>
       <action dev="ggregory" type="add"                due-to="Gary 
Gregory">Add NullInputStream.init().</action>
       <action dev="ggregory" type="add"                due-to="Gary 
Gregory">Add AbstractInputStream and refactor duplicate code.</action>
+      <action dev="ggregory" type="add"                due-to="Gary 
Gregory">Add UnsynchronizedReader.</action>
+      <action dev="ggregory" type="add"                due-to="Gary 
Gregory">Add UnsynchronizedBufferedReader.</action>
       <!-- FIX -->
       <action dev="sebb" type="fix" issue="IO-858">FileUtilsWaitForTest does 
not test anything useful.</action>
       <action dev="ggregory" type="fix"                due-to="Gary 
Gregory">Add missing unit tests.</action>
diff --git 
a/src/main/java/org/apache/commons/io/input/UnsynchronizedBufferedReader.java 
b/src/main/java/org/apache/commons/io/input/UnsynchronizedBufferedReader.java
new file mode 100644
index 000000000..bab791ac2
--- /dev/null
+++ 
b/src/main/java/org/apache/commons/io/input/UnsynchronizedBufferedReader.java
@@ -0,0 +1,463 @@
+/*
+ *  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 static org.apache.commons.io.IOUtils.EOF;
+import static org.apache.commons.io.IOUtils.CR;
+import static org.apache.commons.io.IOUtils.LF;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.io.Reader;
+
+import org.apache.commons.io.IOUtils;
+
+/**
+ * Wraps an existing {@link Reader} and buffers the input <em>without any 
synchronization</em>. Expensive interaction with the underlying reader is 
minimized,
+ * since most (smaller) requests can be satisfied by accessing the buffer 
alone. The drawback is that some extra space is required to hold the buffer and 
that
+ * copying takes place when filling that buffer, but this is usually 
outweighed by the performance benefits.
+ * <p>
+ * A typical application pattern for the class looks like this:
+ * </p>
+ *
+ * <pre>{@code
+ * UnsynchronizedBufferedReader buf = new UnsynchronizedBufferedReader(new 
FileReader("file"));
+ * }</pre>
+ * <p>
+ * Provenance: Apache Harmony's java.io.BufferedReader, renamed, and modified.
+ * </p>
+ *
+ * @see BufferedReader
+ * @see BufferedWriter
+ * @since 2.17.0
+ */
+public class UnsynchronizedBufferedReader extends UnsynchronizedReader {
+
+    private static final char NUL = '\0';
+
+    private final Reader in;
+
+    /**
+     * The characters that can be read and refilled in bulk. We maintain three 
indices into this buffer:
+     *
+     * <pre>
+     *     { X X X X X X X X X X X X - - }
+     *           ^     ^             ^
+     *           |     |             |
+     *         mark   pos           end
+     * </pre>
+     * <p>
+     * Pos points to the next readable character. End is one greater than the 
last readable character. When {@code pos == end}, the buffer is empty and must 
be
+     * {@link #fillBuf() filled} before characters can be read.
+     * </p>
+     * <p>
+     * Mark is the value pos will be set to on calls to {@link #reset()}. Its 
value is in the range {@code [0...pos]}. If the mark is {@code -1}, the buffer
+     * cannot be reset.
+     * </p>
+     * <p>
+     * MarkLimit limits the distance between the mark and the pos. When this 
limit is exceeded, {@link #reset()} is permitted (but not required) to throw an
+     * exception. For shorter distances, {@link #reset()} shall not throw 
(unless the reader is closed).
+     * </p>
+     */
+    private char[] buf;
+
+    private int pos;
+
+    private int end;
+
+    private int mark = -1;
+
+    private int markLimit = -1;
+
+    /**
+     * Constructs a new BufferedReader on the Reader {@code in}. The buffer 
gets the default size (8 KB).
+     *
+     * @param in the Reader that is buffered.
+     */
+    public UnsynchronizedBufferedReader(final Reader in) {
+        this.in = in;
+        buf = new char[IOUtils.DEFAULT_BUFFER_SIZE];
+    }
+
+    /**
+     * Constructs a new BufferedReader on the Reader {@code in}. The buffer 
size is specified by the parameter {@code size}.
+     *
+     * @param in   the Reader that is buffered.
+     * @param size the size of the buffer to allocate.
+     * @throws IllegalArgumentException if {@code size <= 0}.
+     */
+    public UnsynchronizedBufferedReader(final Reader in, final int size) {
+        if (size <= 0) {
+            throw new IllegalArgumentException("size <= 0");
+        }
+        this.in = in;
+        buf = new char[size];
+    }
+
+    private void checkClosed() throws IOException {
+        if (isClosed()) {
+            throw new IOException("Closed");
+        }
+    }
+
+    /**
+     * Peeks at the next input character, refilling the buffer if necessary. 
If this character is a newline character ("\n"), it is discarded.
+     */
+    final void chompNewline() throws IOException {
+        if ((pos != end || fillBuf() != EOF) && buf[pos] == LF) {
+            pos++;
+        }
+    }
+
+    /**
+     * Closes this reader. This implementation closes the buffered source 
reader and releases the buffer. Nothing is done if this reader has already been
+     * closed.
+     *
+     * @throws IOException if an error occurs while closing this reader.
+     */
+    @Override
+    public void close() throws IOException {
+        if (!isClosed()) {
+            in.close();
+            buf = null;
+        }
+    }
+
+    /**
+     * Populates the buffer with data. It is an error to call this method when 
the buffer still contains data; ie. if {@code pos < end}.
+     *
+     * @return the number of bytes read into the buffer, or -1 if the end of 
the source stream has been reached.
+     */
+    private int fillBuf() throws IOException {
+        // assert(pos == end);
+
+        if (mark == EOF || pos - mark >= markLimit) {
+            /* mark isn't set or has exceeded its limit. use the whole buffer 
*/
+            final int result = in.read(buf, 0, buf.length);
+            if (result > 0) {
+                mark = -1;
+                pos = 0;
+                end = result;
+            }
+            return result;
+        }
+
+        if (mark == 0 && markLimit > buf.length) {
+            /* the only way to make room when mark=0 is by growing the buffer 
*/
+            int newLength = buf.length * 2;
+            if (newLength > markLimit) {
+                newLength = markLimit;
+            }
+            final char[] newbuf = new char[newLength];
+            System.arraycopy(buf, 0, newbuf, 0, buf.length);
+            buf = newbuf;
+        } else if (mark > 0) {
+            /* make room by shifting the buffered data to left mark positions 
*/
+            System.arraycopy(buf, mark, buf, 0, buf.length - mark);
+            pos -= mark;
+            end -= mark;
+            mark = 0;
+        }
+
+        /* Set the new position and mark position */
+        final int count = in.read(buf, pos, buf.length - pos);
+        if (count != EOF) {
+            end += count;
+        }
+        return count;
+    }
+
+    /**
+     * Tests whether or not this reader is closed.
+     *
+     * @return {@code true} if this reader is closed, {@code false} otherwise.
+     */
+    private boolean isClosed() {
+        return buf == null;
+    }
+
+    /**
+     * Sets a mark position in this reader. The parameter {@code markLimit} 
indicates how many characters can be read before the mark is invalidated. 
Calling
+     * {@link #reset()} will reposition the reader back to the marked position 
if {@code markLimit} has not been surpassed.
+     *
+     * @param markLimit the number of characters that can be read before the 
mark is invalidated.
+     * @throws IllegalArgumentException if {@code markLimit < 0}.
+     * @throws IOException              if an error occurs while setting a 
mark in this reader.
+     * @see #markSupported()
+     * @see #reset()
+     */
+    @Override
+    public void mark(final int markLimit) throws IOException {
+        if (markLimit < 0) {
+            throw new IllegalArgumentException();
+        }
+        checkClosed();
+        this.markLimit = markLimit;
+        mark = pos;
+    }
+
+    /**
+     * Tests whether this reader supports the {@link #mark(int)} and {@link 
#reset()} methods. This implementation returns {@code true}.
+     *
+     * @return {@code true} for {@code BufferedReader}.
+     * @see #mark(int)
+     * @see #reset()
+     */
+    @Override
+    public boolean markSupported() {
+        return true;
+    }
+
+    /**
+     * Reads a single character from this reader and returns it with the two 
higher-order bytes set to 0. If possible, BufferedReader returns a character 
from
+     * the buffer. If there are no characters available in the buffer, it 
fills the buffer and then returns a character. It returns -1 if there are no 
more
+     * characters in the source reader.
+     *
+     * @return the character read or -1 if the end of the source reader has 
been reached.
+     * @throws IOException if this reader is closed or some other I/O error 
occurs.
+     */
+    @Override
+    public int read() throws IOException {
+        checkClosed();
+        /* Are there buffered characters available? */
+        if (pos < end || fillBuf() != EOF) {
+            return buf[pos++];
+        }
+        return EOF;
+    }
+
+    /**
+     * Reads at most {@code length} characters from this reader and stores 
them at {@code offset} in the character array {@code buffer}. Returns the 
number of
+     * characters actually read or -1 if the end of the source reader has been 
reached. If all the buffered characters have been used, a mark has not been set
+     * and the requested number of characters is larger than this readers 
buffer size, BufferedReader bypasses the buffer and simply places the results 
directly
+     * into {@code buffer}.
+     *
+     * @param buffer the character array to store the characters read.
+     * @param offset the initial position in {@code buffer} to store the bytes 
read from this reader.
+     * @param length the maximum number of characters to read, must be 
non-negative.
+     * @return number of characters read or -1 if the end of the source reader 
has been reached.
+     * @throws IndexOutOfBoundsException if {@code offset < 0} or {@code 
length < 0}, or if {@code offset + length} is greater than the size of {@code 
buffer}.
+     * @throws IOException               if this reader is closed or some 
other I/O error occurs.
+     */
+    @Override
+    public int read(final char[] buffer, int offset, final int length) throws 
IOException {
+        checkClosed();
+        if (offset < 0 || offset > buffer.length - length || length < 0) {
+            throw new IndexOutOfBoundsException();
+        }
+        int outstanding = length;
+        while (outstanding > 0) {
+
+            /*
+             * If there are bytes in the buffer, grab those first.
+             */
+            final int available = end - pos;
+            if (available > 0) {
+                final int count = available >= outstanding ? outstanding : 
available;
+                System.arraycopy(buf, pos, buffer, offset, count);
+                pos += count;
+                offset += count;
+                outstanding -= count;
+            }
+
+            /*
+             * Before attempting to read from the underlying stream, make sure 
we really, really want to. We won't bother if we're done, or if we've already 
got
+             * some bytes and reading from the underlying stream would block.
+             */
+            if (outstanding == 0 || outstanding < length && !in.ready()) {
+                break;
+            }
+
+            // assert(pos == end);
+
+            /*
+             * If we're unmarked and the requested size is greater than our 
buffer, read the bytes directly into the caller's buffer. We don't read into 
smaller
+             * buffers because that could result in a many reads.
+             */
+            if ((mark == -1 || pos - mark >= markLimit) && outstanding >= 
buf.length) {
+                final int count = in.read(buffer, offset, outstanding);
+                if (count > 0) {
+                    outstanding -= count;
+                    mark = -1;
+                }
+
+                break; // assume the source stream gave us all that it could
+            }
+
+            if (fillBuf() == EOF) {
+                break; // source is exhausted
+            }
+        }
+
+        final int count = length - outstanding;
+        return count > 0 || count == length ? count : EOF;
+    }
+
+    /**
+     * Returns the next line of text available from this reader. A line is 
represented by zero or more characters followed by {@code LF}, {@code CR},
+     * {@code "\r\n"} or the end of the reader. The string does not include 
the newline sequence.
+     *
+     * @return the contents of the line or {@code null} if no characters were 
read before the end of the reader has been reached.
+     * @throws IOException if this reader is closed or some other I/O error 
occurs.
+     */
+    public String readLine() throws IOException {
+        checkClosed();
+        /* has the underlying stream been exhausted? */
+        if (pos == end && fillBuf() == EOF) {
+            return null;
+        }
+        for (int charPos = pos; charPos < end; charPos++) {
+            final char ch = buf[charPos];
+            if (ch > CR) {
+                continue;
+            }
+            if (ch == LF) {
+                final String res = new String(buf, pos, charPos - pos);
+                pos = charPos + 1;
+                return res;
+            } else if (ch == CR) {
+                final String res = new String(buf, pos, charPos - pos);
+                pos = charPos + 1;
+                if ((pos < end || fillBuf() != EOF) && buf[pos] == LF) {
+                    pos++;
+                }
+                return res;
+            }
+        }
+
+        char eol = NUL;
+        final StringBuilder result = new StringBuilder(80);
+        /* Typical Line Length */
+
+        result.append(buf, pos, end - pos);
+        while (true) {
+            pos = end;
+
+            /* Are there buffered characters available? */
+            if (eol == LF) {
+                return result.toString();
+            }
+            // attempt to fill buffer
+            if (fillBuf() == EOF) {
+                // characters or null.
+                return result.length() > 0 || eol != NUL ? result.toString() : 
null;
+            }
+            for (int charPos = pos; charPos < end; charPos++) {
+                final char c = buf[charPos];
+                if (eol == NUL) {
+                    if (c == LF || c == CR) {
+                        eol = c;
+                    }
+                } else {
+                    if (eol == CR && c == LF) {
+                        if (charPos > pos) {
+                            result.append(buf, pos, charPos - pos - 1);
+                        }
+                        pos = charPos + 1;
+                    } else {
+                        if (charPos > pos) {
+                            result.append(buf, pos, charPos - pos - 1);
+                        }
+                        pos = charPos;
+                    }
+                    return result.toString();
+                }
+            }
+            if (eol == NUL) {
+                result.append(buf, pos, end - pos);
+            } else {
+                result.append(buf, pos, end - pos - 1);
+            }
+        }
+    }
+
+    /**
+     * Tests whether this reader is ready to be read without blocking.
+     *
+     * @return {@code true} if this reader will not block when {@code read} is 
called, {@code false} if unknown or blocking will occur.
+     * @throws IOException if this reader is closed or some other I/O error 
occurs.
+     * @see #read()
+     * @see #read(char[], int, int)
+     * @see #readLine()
+     */
+    @Override
+    public boolean ready() throws IOException {
+        checkClosed();
+        return end - pos > 0 || in.ready();
+    }
+
+    /**
+     * Resets this reader's position to the last {@code mark()} location. 
Invocations of {@code read()} and {@code skip()} will occur from this new 
location.
+     *
+     * @throws IOException if this reader is closed or no mark has been set.
+     * @see #mark(int)
+     * @see #markSupported()
+     */
+    @Override
+    public void reset() throws IOException {
+        checkClosed();
+        if (mark == -1) {
+            throw new IOException("mark == -1");
+        }
+        pos = mark;
+    }
+
+    /**
+     * Skips {@code amount} characters in this reader. Subsequent {@code 
read()}s will not return these characters unless {@code reset()} is used. 
Skipping
+     * characters may invalidate a mark if {@code markLimit} is surpassed.
+     *
+     * @param amount the maximum number of characters to skip.
+     * @return the number of characters actually skipped.
+     * @throws IllegalArgumentException if {@code amount < 0}.
+     * @throws IOException              if this reader is closed or some other 
I/O error occurs.
+     * @see #mark(int)
+     * @see #markSupported()
+     * @see #reset()
+     */
+    @Override
+    public long skip(final long amount) throws IOException {
+        if (amount < 0) {
+            throw new IllegalArgumentException();
+        }
+        checkClosed();
+        if (amount < 1) {
+            return 0;
+        }
+        if (end - pos >= amount) {
+            pos += amount;
+            return amount;
+        }
+
+        long read = end - pos;
+        pos = end;
+        while (read < amount) {
+            if (fillBuf() == EOF) {
+                return read;
+            }
+            if (end - pos >= amount - read) {
+                pos += amount - read;
+                return amount;
+            }
+            // Couldn't get all the characters, skip what we read
+            read += end - pos;
+            pos = end;
+        }
+        return amount;
+    }
+}
diff --git 
a/src/main/java/org/apache/commons/io/input/UnsynchronizedReader.java 
b/src/main/java/org/apache/commons/io/input/UnsynchronizedReader.java
new file mode 100644
index 000000000..1350d2153
--- /dev/null
+++ b/src/main/java/org/apache/commons/io/input/UnsynchronizedReader.java
@@ -0,0 +1,78 @@
+/*
+ *  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 static org.apache.commons.io.IOUtils.EOF;
+
+import java.io.IOException;
+import java.io.Reader;
+
+import org.apache.commons.io.IOUtils;
+
+/**
+ * A {@link Reader} without any of the superclass' synchronization.
+ *
+ * @since 2.17.0
+ */
+public abstract class UnsynchronizedReader extends Reader {
+
+    /**
+     * The maximum skip-buffer size.
+     */
+    private static final int MAX_SKIP_BUFFER_SIZE = 
IOUtils.DEFAULT_BUFFER_SIZE;
+
+    /**
+     * The skip buffer, defaults to null until allocated in {@link 
UnsynchronizedReader#skip(long)}.
+     */
+    private char skipBuffer[];
+
+    /**
+     * Skips characters by reading from this instance.
+     *
+     * This method will <em>block</em> until:
+     * <ul>
+     * <li>some characters are available,</li>
+     * <li>an I/O error occurs, or</li>
+     * <li>the end of the stream is reached.</li>
+     * </ul>
+     *
+     * @param n The number of characters to skip.
+     * @return The number of characters actually skipped.
+     * @throws IllegalArgumentException If {@code n} is negative.
+     * @throws IOException              If an I/O error occurs.
+     */
+    @Override
+    public long skip(final long n) throws IOException {
+        if (n < 0L) {
+            throw new IllegalArgumentException("skip value < 0");
+        }
+        final int bufSize = (int) Math.min(n, MAX_SKIP_BUFFER_SIZE);
+        if (skipBuffer == null || skipBuffer.length < bufSize) {
+            skipBuffer = new char[bufSize];
+        }
+        long remaining = n;
+        while (remaining > 0) {
+            final int countOrEof = read(skipBuffer, 0, (int) 
Math.min(remaining, bufSize));
+            if (countOrEof == EOF) {
+                break;
+            }
+            remaining -= countOrEof;
+        }
+        return n - remaining;
+    }
+}
diff --git 
a/src/test/java/org/apache/commons/io/input/UnsynchronizedBufferedReaderTest.java
 
b/src/test/java/org/apache/commons/io/input/UnsynchronizedBufferedReaderTest.java
new file mode 100644
index 000000000..909a20cc0
--- /dev/null
+++ 
b/src/test/java/org/apache/commons/io/input/UnsynchronizedBufferedReaderTest.java
@@ -0,0 +1,457 @@
+/*
+ *  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 static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.ByteArrayInputStream;
+import java.io.CharArrayReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.PipedReader;
+import java.io.Reader;
+import java.io.StringReader;
+
+import org.apache.commons.io.IOUtils;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests {@link UnsynchronizedBufferedReader}.
+ * <p>
+ * Provenance: Apache Harmony {@code BufferedReaderTest}, copied, and modified.
+ * </p>
+ */
+public class UnsynchronizedBufferedReaderTest {
+
+    private UnsynchronizedBufferedReader br;
+
+    private final String testString = 
"Test_All_Tests\nTest_java_io_BufferedInputStream\nTest_java_io_BufferedOutputStream\nTest_java_io_ByteArrayInputStream\n"
+            + 
"Test_java_io_ByteArrayOutputStream\nTest_java_io_DataInputStream\nTest_java_io_File\nTest_java_io_FileDescriptor\nTest_java_io_FileInputStream\n"
+            + 
"Test_java_io_FileNotFoundException\nTest_java_io_FileOutputStream\nTest_java_io_FilterInputStream\nTest_java_io_FilterOutputStream\n"
+            + 
"Test_java_io_InputStream\nTest_java_io_IOException\nTest_java_io_OutputStream\nTest_java_io_PrintStream\nTest_java_io_RandomAccessFile\n"
+            + 
"Test_java_io_SyncFailedException\nTest_java_lang_AbstractMethodError\nTest_java_lang_ArithmeticException\n"
+            + 
"Test_java_lang_ArrayIndexOutOfBoundsException\nTest_java_lang_ArrayStoreException\nTest_java_lang_Boolean\nTest_java_lang_Byte\n"
+            + 
"Test_java_lang_Character\nTest_java_lang_Class\nTest_java_lang_ClassCastException\nTest_java_lang_ClassCircularityError\n"
+            + 
"Test_java_lang_ClassFormatError\nTest_java_lang_ClassLoader\nTest_java_lang_ClassNotFoundException\nTest_java_lang_CloneNotSupportedException\n"
+            + 
"Test_java_lang_Double\nTest_java_lang_Error\nTest_java_lang_Exception\nTest_java_lang_ExceptionInInitializerError\nTest_java_lang_Float\n"
+            + 
"Test_java_lang_IllegalAccessError\nTest_java_lang_IllegalAccessException\nTest_java_lang_IllegalArgumentException\n"
+            + 
"Test_java_lang_IllegalMonitorStateException\nTest_java_lang_IllegalThreadStateException\nTest_java_lang_IncompatibleClassChangeError\n"
+            + 
"Test_java_lang_IndexOutOfBoundsException\nTest_java_lang_InstantiationError\nTest_java_lang_InstantiationException\nTest_java_lang_Integer\n"
+            + 
"Test_java_lang_InternalError\nTest_java_lang_InterruptedException\nTest_java_lang_LinkageError\nTest_java_lang_Long\nTest_java_lang_Math\n"
+            + 
"Test_java_lang_NegativeArraySizeException\nTest_java_lang_NoClassDefFoundError\nTest_java_lang_NoSuchFieldError\n"
+            + 
"Test_java_lang_NoSuchMethodError\nTest_java_lang_NullPointerException\nTest_java_lang_Number\nTest_java_lang_NumberFormatException\n"
+            + 
"Test_java_lang_Object\nTest_java_lang_OutOfMemoryError\nTest_java_lang_RuntimeException\nTest_java_lang_SecurityManager\nTest_java_lang_Short\n"
+            + 
"Test_java_lang_StackOverflowError\nTest_java_lang_String\nTest_java_lang_StringBuffer\nTest_java_lang_StringIndexOutOfBoundsException\n"
+            + 
"Test_java_lang_System\nTest_java_lang_Thread\nTest_java_lang_ThreadDeath\nTest_java_lang_ThreadGroup\nTest_java_lang_Throwable\n"
+            + 
"Test_java_lang_UnknownError\nTest_java_lang_UnsatisfiedLinkError\nTest_java_lang_VerifyError\nTest_java_lang_VirtualMachineError\n"
+            + 
"Test_java_lang_vm_Image\nTest_java_lang_vm_MemorySegment\nTest_java_lang_vm_ROMStoreException\nTest_java_lang_vm_VM\nTest_java_lang_Void\n"
+            + 
"Test_java_net_BindException\nTest_java_net_ConnectException\nTest_java_net_DatagramPacket\nTest_java_net_DatagramSocket\n"
+            + 
"Test_java_net_DatagramSocketImpl\nTest_java_net_InetAddress\nTest_java_net_NoRouteToHostException\nTest_java_net_PlainDatagramSocketImpl\n"
+            + 
"Test_java_net_PlainSocketImpl\nTest_java_net_Socket\nTest_java_net_SocketException\nTest_java_net_SocketImpl\nTest_java_net_SocketInputStream\n"
+            + 
"Test_java_net_SocketOutputStream\nTest_java_net_UnknownHostException\nTest_java_util_ArrayEnumerator\nTest_java_util_Date\n"
+            + 
"Test_java_util_EventObject\nTest_java_util_HashEnumerator\nTest_java_util_Hashtable\nTest_java_util_Properties\nTest_java_util_ResourceBundle\n"
+            + "Test_java_util_tm\nTest_java_util_Vector\n";
+
+    /**
+     * Tears down the fixture, for example, close a network connection. This 
method is called after a test is executed.
+     */
+    @AfterEach
+    protected void afterEach() {
+        IOUtils.closeQuietly(br);
+    }
+
+    private void assertLines(final String in, final String... lines) throws 
IOException {
+        try (UnsynchronizedBufferedReader bufferedReader = new 
UnsynchronizedBufferedReader(new StringReader(in))) {
+            for (final String line : lines) {
+                assertEquals(line, bufferedReader.readLine());
+            }
+            assertNull(bufferedReader.readLine());
+        }
+    }
+
+    /**
+     * Tests {@link UnsynchronizedBufferedReader#close()}.
+     *
+     * @throws IOException test failure.
+     */
+    @Test
+    public void test_close() throws IOException {
+        // Test for method void UnsynchronizedBufferedReader.close()
+        br = new UnsynchronizedBufferedReader(new StringReader(testString));
+        br.close();
+        assertThrows(IOException.class, br::read);
+    }
+
+    /**
+     * Tests {@link UnsynchronizedBufferedReader#mark(int)}.
+     *
+     * @throws IOException test failure.
+     */
+    @Test
+    public void test_markI() throws IOException {
+        // Test for method void UnsynchronizedBufferedReader.mark(int)
+        char[] buf = null;
+        br = new UnsynchronizedBufferedReader(new StringReader(testString));
+        br.skip(500);
+        br.mark(1000);
+        br.skip(250);
+        br.reset();
+        buf = new char[testString.length()];
+        br.read(buf, 0, 500);
+        assertTrue(testString.substring(500, 1000).equals(new String(buf, 0, 
500)));
+
+        br = new UnsynchronizedBufferedReader(new StringReader(testString), 
800);
+        br.skip(500);
+        br.mark(250);
+        br.read(buf, 0, 1000);
+        assertThrows(IOException.class, br::reset);
+
+        final char[] chars = new char[256];
+        for (int i = 0; i < 256; i++) {
+            chars[i] = (char) i;
+        }
+
+        try (Reader in = new UnsynchronizedBufferedReader(new StringReader(new 
String(chars)), 12)) {
+            in.skip(6);
+            in.mark(14);
+            in.read(new char[14], 0, 14);
+            in.reset();
+            assertTrue(in.read() == (char) 6 && in.read() == (char) 7);
+        }
+        try (Reader in = new UnsynchronizedBufferedReader(new StringReader(new 
String(chars)), 12)) {
+            in.skip(6);
+            in.mark(8);
+            in.skip(7);
+            in.reset();
+            assertTrue(in.read() == (char) 6 && in.read() == (char) 7);
+        }
+        try (UnsynchronizedBufferedReader br = new 
UnsynchronizedBufferedReader(new StringReader("01234"), 2)) {
+            br.mark(3);
+            final char[] carray = new char[3];
+            final int result = br.read(carray);
+            assertEquals(3, result);
+            assertEquals('0', carray[0]);
+            assertEquals('1', carray[1]);
+            assertEquals('2', carray[2]);
+            assertEquals('3', br.read());
+        }
+        try (UnsynchronizedBufferedReader br = new 
UnsynchronizedBufferedReader(new StringReader("01234"), 2)) {
+            br.mark(3);
+            final char[] carray = new char[4];
+            final int result = br.read(carray);
+            assertEquals(4, result);
+            assertEquals('0', carray[0]);
+            assertEquals('1', carray[1]);
+            assertEquals('2', carray[2]);
+            assertEquals('3', carray[3]);
+            assertEquals('4', br.read());
+            assertEquals(-1, br.read());
+        }
+        try (UnsynchronizedBufferedReader reader = new 
UnsynchronizedBufferedReader(new StringReader("01234"))) {
+            reader.mark(Integer.MAX_VALUE);
+            reader.read();
+        }
+    }
+
+    /**
+     * Tests {@link UnsynchronizedBufferedReader#markSupported()}.
+     */
+    @Test
+    public void test_markSupported() {
+        // Test for method boolean UnsynchronizedBufferedReader.markSupported()
+        br = new UnsynchronizedBufferedReader(new StringReader(testString));
+        assertTrue(br.markSupported());
+    }
+
+    /**
+     * Tests {@link UnsynchronizedBufferedReader#read()}.
+     *
+     * @throws IOException test failure.
+     */
+    @Test
+    public void test_read() throws IOException {
+        // Test for method int UnsynchronizedBufferedReader.read()
+        br = new UnsynchronizedBufferedReader(new StringReader(testString));
+        final int r = br.read();
+        assertTrue(testString.charAt(0) == r);
+        br = new UnsynchronizedBufferedReader(new StringReader(new String(new 
char[] { '\u8765' })));
+        assertTrue(br.read() == '\u8765');
+        //
+        final char[] chars = new char[256];
+        for (int i = 0; i < 256; i++) {
+            chars[i] = (char) i;
+        }
+        try (Reader in = new UnsynchronizedBufferedReader(new StringReader(new 
String(chars)), 12)) {
+            assertEquals(0, in.read()); // Fill the
+            // buffer
+            final char[] buf = new char[14];
+            in.read(buf, 0, 14); // Read greater than the buffer
+            assertTrue(new String(buf).equals(new String(chars, 1, 14)));
+            assertEquals(15, in.read()); // Check next byte
+        }
+        //
+        // regression test for HARMONY-841
+        try (Reader reader = new UnsynchronizedBufferedReader(new 
CharArrayReader(new char[5], 1, 0), 2)) {
+            assertTrue(reader.read() == -1);
+        }
+    }
+
+    /**
+     * Tests {@link UnsynchronizedBufferedReader#read(char[], int, int)}.
+     *
+     * @throws IOException test failure.
+     */
+    @Test
+    public void test_read_$CII_Exception() throws IOException {
+        br = new UnsynchronizedBufferedReader(new StringReader(testString));
+        final char[] nullCharArray = null;
+        final char[] charArray = testString.toCharArray();
+        assertThrows(IndexOutOfBoundsException.class, () -> 
br.read(nullCharArray, -1, -1));
+        assertThrows(IndexOutOfBoundsException.class, () -> 
br.read(nullCharArray, -1, 0));
+        assertThrows(NullPointerException.class, () -> br.read(nullCharArray, 
0, -1));
+        assertThrows(NullPointerException.class, () -> br.read(nullCharArray, 
0, 0));
+        assertThrows(NullPointerException.class, () -> br.read(nullCharArray, 
0, 1));
+        assertThrows(IndexOutOfBoundsException.class, () -> br.read(charArray, 
-1, -1));
+        assertThrows(IndexOutOfBoundsException.class, () -> br.read(charArray, 
-1, 0));
+
+        br.read(charArray, 0, 0);
+        br.read(charArray, 0, charArray.length);
+        br.read(charArray, charArray.length, 0);
+
+        assertThrows(IndexOutOfBoundsException.class, () -> br.read(charArray, 
charArray.length + 1, 0));
+        assertThrows(IndexOutOfBoundsException.class, () -> br.read(charArray, 
charArray.length + 1, 1));
+
+        br.close();
+
+        assertThrows(IOException.class, () -> br.read(nullCharArray, -1, -1));
+        assertThrows(IOException.class, () -> br.read(charArray, -1, 0));
+        assertThrows(IOException.class, () -> br.read(charArray, 0, -1));
+    }
+
+    /**
+     * Tests {@link UnsynchronizedBufferedReader#read(char[], int, int)}.
+     *
+     * @throws IOException test failure.
+     */
+    @Test
+    public void test_read$CII() throws IOException {
+        final char[] ca = new char[2];
+        try (UnsynchronizedBufferedReader toRet = new 
UnsynchronizedBufferedReader(new InputStreamReader(new ByteArrayInputStream(new 
byte[0])))) {
+            /* Null buffer should throw NPE even when len == 0 */
+            assertThrows(NullPointerException.class, () -> toRet.read(null, 1, 
0));
+            toRet.close();
+            assertThrows(IOException.class, () -> toRet.read(null, 1, 0));
+            /* Closed reader should throw IOException reading zero bytes */
+            assertThrows(IOException.class, () -> toRet.read(ca, 0, 0));
+            /*
+             * Closed reader should throw IOException in preference to index 
out of bounds
+             */
+            // Read should throw IOException before
+            // ArrayIndexOutOfBoundException
+            assertThrows(IOException.class, () -> toRet.read(ca, 1, 5));
+        }
+        // Test to ensure that a drained stream returns 0 at EOF
+        try (UnsynchronizedBufferedReader toRet2 = new 
UnsynchronizedBufferedReader(new InputStreamReader(new ByteArrayInputStream(new 
byte[2])))) {
+            assertEquals(2, toRet2.read(ca, 0, 2));
+            assertEquals(-1, toRet2.read(ca, 0, 2));
+            assertEquals(0, toRet2.read(ca, 0, 0));
+        }
+
+        // Test for method int UnsynchronizedBufferedReader.read(char [], int, 
int)
+        final char[] buf = new char[testString.length()];
+        br = new UnsynchronizedBufferedReader(new StringReader(testString));
+        br.read(buf, 50, 500);
+        assertTrue(new String(buf, 50, 500).equals(testString.substring(0, 
500)));
+
+        try (UnsynchronizedBufferedReader bufin = new 
UnsynchronizedBufferedReader(new Reader() {
+            int size = 2, pos = 0;
+
+            char[] contents = new char[size];
+
+            @Override
+            public void close() throws IOException {
+            }
+
+            @Override
+            public int read() throws IOException {
+                if (pos >= size) {
+                    throw new IOException("Read past end of data");
+                }
+                return contents[pos++];
+            }
+
+            @Override
+            public int read(final char[] buf, final int off, final int len) 
throws IOException {
+                if (pos >= size) {
+                    throw new IOException("Read past end of data");
+                }
+                int toRead = len;
+                if (toRead > size - pos) {
+                    toRead = size - pos;
+                }
+                System.arraycopy(contents, pos, buf, off, toRead);
+                pos += toRead;
+                return toRead;
+            }
+
+            @Override
+            public boolean ready() throws IOException {
+                return size - pos > 0;
+            }
+        })) {
+            bufin.read();
+            final int result = bufin.read(new char[2], 0, 2);
+            assertTrue(result == 1);
+        }
+        // regression for HARMONY-831
+        try (Reader reader = new UnsynchronizedBufferedReader(new 
PipedReader(), 9)) {
+            assertThrows(IndexOutOfBoundsException.class, () -> 
reader.read(new char[] {}, 7, 0));
+        }
+
+        // Regression for HARMONY-54
+        final char[] ch = {};
+        @SuppressWarnings("resource")
+        final UnsynchronizedBufferedReader reader = new 
UnsynchronizedBufferedReader(new CharArrayReader(ch));
+        // Check exception thrown when the reader is open.
+        assertThrows(NullPointerException.class, () -> reader.read(null, 1, 
0));
+
+        // Now check IOException is thrown in preference to
+        // NullPointerexception when the reader is closed.
+        reader.close();
+        assertThrows(IOException.class, () -> reader.read(null, 1, 0));
+
+        // And check that the IOException is thrown before
+        // ArrayIndexOutOfBoundException
+        assertThrows(IOException.class, () -> reader.read(ch, 0, 42));
+    }
+
+    /**
+     * Tests {@link UnsynchronizedBufferedReader#readLine()}.
+     *
+     * @throws IOException test failure.
+     */
+    @Test
+    public void test_readLine() throws IOException {
+        // Test for method java.lang.String 
UnsynchronizedBufferedReader.readLine()
+        br = new UnsynchronizedBufferedReader(new StringReader(testString));
+        final String r = br.readLine();
+        assertEquals("Test_All_Tests", r);
+    }
+
+    /**
+     * The spec says that BufferedReader.readLine() considers only "\r", "\n" 
and "\r\n" to be line separators. We must not permit additional separator
+     * characters.
+     *
+     * @throws IOException test failure.
+     */
+    @Test
+    public void test_readLine_IgnoresEbcdic85Characters() throws IOException {
+        assertLines("A\u0085B", "A\u0085B");
+    }
+
+    @Test
+    public void test_readLine_Separators() throws IOException {
+        assertLines("A\nB\nC", "A", "B", "C");
+        assertLines("A\rB\rC", "A", "B", "C");
+        assertLines("A\r\nB\r\nC", "A", "B", "C");
+        assertLines("A\n\rB\n\rC", "A", "", "B", "", "C");
+        assertLines("A\n\nB\n\nC", "A", "", "B", "", "C");
+        assertLines("A\r\rB\r\rC", "A", "", "B", "", "C");
+        assertLines("A\n\n", "A", "");
+        assertLines("A\n\r", "A", "");
+        assertLines("A\r\r", "A", "");
+        assertLines("A\r\n", "A");
+        assertLines("A\r\n\r\n", "A", "");
+    }
+
+    /**
+     * Tests {@link UnsynchronizedBufferedReader#ready()}.
+     *
+     * @throws IOException test failure.
+     */
+    @Test
+    public void test_ready() throws IOException {
+        // Test for method boolean UnsynchronizedBufferedReader.ready()
+        br = new UnsynchronizedBufferedReader(new StringReader(testString));
+        assertTrue(br.ready());
+    }
+
+    /**
+     * Tests {@link UnsynchronizedBufferedReader#reset()}.
+     *
+     * @throws IOException
+     */
+    @Test
+    public void test_reset() throws IOException {
+        // Test for method void UnsynchronizedBufferedReader.reset()
+        br = new UnsynchronizedBufferedReader(new StringReader(testString));
+        br.skip(500);
+        br.mark(900);
+        br.skip(500);
+        br.reset();
+        final char[] buf = new char[testString.length()];
+        br.read(buf, 0, 500);
+        assertTrue(testString.substring(500, 1000).equals(new String(buf, 0, 
500)));
+        br = new UnsynchronizedBufferedReader(new StringReader(testString));
+        br.skip(500);
+        assertThrows(IOException.class, br::reset);
+    }
+
+    @Test
+    public void test_reset_IOException() throws Exception {
+        final int[] expected = { '1', '2', '3', '4', '5', '6', '7', '8', '9', 
'0', -1 };
+        br = new UnsynchronizedBufferedReader(new StringReader("1234567890"), 
9);
+        br.mark(9);
+        for (int i = 0; i < 11; i++) {
+            assertEquals(expected[i], br.read());
+        }
+        assertThrows(IOException.class, br::reset);
+        for (int i = 0; i < 11; i++) {
+            assertEquals(-1, br.read());
+        }
+
+        br = new UnsynchronizedBufferedReader(new StringReader("1234567890"));
+        br.mark(10);
+        for (int i = 0; i < 10; i++) {
+            assertEquals(expected[i], br.read());
+        }
+        br.reset();
+        for (int i = 0; i < 11; i++) {
+            assertEquals(expected[i], br.read());
+        }
+    }
+
+    /**
+     * Tests {@link UnsynchronizedBufferedReader#skip(long)}.
+     *
+     * @throws IOException
+     */
+    @Test
+    public void test_skipJ() throws IOException {
+        // Test for method long UnsynchronizedBufferedReader.skip(long)
+        br = new UnsynchronizedBufferedReader(new StringReader(testString));
+        br.skip(500);
+        final char[] buf = new char[testString.length()];
+        br.read(buf, 0, 500);
+        assertTrue(testString.substring(500, 1000).equals(new String(buf, 0, 
500)));
+    }
+}


Reply via email to