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

commit fb7d4b109124a5f0b38b3ee66df77c1cf2ebb3ae
Author: Gary Gregory <garydgreg...@gmail.com>
AuthorDate: Wed Feb 1 09:32:04 2023 -0500

    [IO-786] Add UnsynchronizedBufferedInputStream
---
 src/changes/changes.xml                            |   3 +
 .../input/UnsynchronizedBufferedInputStream.java   | 375 ++++++++++++++++
 .../UnsynchronizedBufferedInputStreamTest.java     | 484 +++++++++++++++++++++
 3 files changed, 862 insertions(+)

diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 412f94ab..0c0a1c7e 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -460,6 +460,9 @@ The <action> type attribute can be add,update,fix,remove.
       <action issue="IO-786" dev="ggregory" type="add" due-to="Gary Gregory">
         Add UnsynchronizedFilterInputStream.
       </action>
+      <action issue="IO-786" dev="ggregory" type="add" due-to="Gary Gregory, 
Benoit Tellier">
+        Add UnsynchronizedBufferedInputStream.
+      </action>
       <!-- UPDATE -->
       <action dev="kinow" type="update" due-to="Dependabot, Gary Gregory">
         Bump actions/cache from 2.1.6 to 3.0.10 #307, #337, #393.
diff --git 
a/src/main/java/org/apache/commons/io/input/UnsynchronizedBufferedInputStream.java
 
b/src/main/java/org/apache/commons/io/input/UnsynchronizedBufferedInputStream.java
new file mode 100644
index 00000000..2352d9eb
--- /dev/null
+++ 
b/src/main/java/org/apache/commons/io/input/UnsynchronizedBufferedInputStream.java
@@ -0,0 +1,375 @@
+/*
+ *  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.BufferedInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.apache.commons.io.IOUtils;
+
+/**
+ * An unsynchronized version of {@link BufferedInputStream}, not thread-safe.
+ * <p>
+ * Wraps an existing {@link InputStream} and <em>buffers</em> the input. 
Expensive interaction with the underlying input stream 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>
+ * <p>
+ * A typical application pattern for the class looks like this:
+ * </p>
+ *
+ * <pre>
+ * UnsynchronizedBufferedInputStream buf = new 
UnsynchronizedBufferedInputStream(new FileInputStream(&quot;file.java&quot;));
+ * </pre>
+ * <p>
+ * Provenance: Apache Harmony and modified.
+ * </p>
+ *
+ * @see BufferedInputStream
+ * @since 2.12.0
+ */
+//@NotThreadSafe
+public class UnsynchronizedBufferedInputStream extends 
UnsynchronizedFilterInputStream {
+    /**
+     * The buffer containing the current bytes read from the target 
InputStream.
+     */
+    protected volatile byte[] buf;
+
+    /**
+     * The total number of bytes inside the byte array {@code buf}.
+     */
+    protected int count;
+
+    /**
+     * The current limit, which when passed, invalidates the current mark.
+     */
+    protected int marklimit;
+
+    /**
+     * The currently marked position. -1 indicates no mark has been set or the 
mark has been invalidated.
+     */
+    protected int markpos = IOUtils.EOF;
+
+    /**
+     * The current position within the byte array {@code buf}.
+     */
+    protected int pos;
+
+    /**
+     * Constructs a new {@code BufferedInputStream} on the {@link InputStream} 
{@code in}. The default buffer size (8 KB) is allocated and all reads can now be
+     * filtered through this stream.
+     *
+     * @param in the InputStream the buffer reads from.
+     */
+    public UnsynchronizedBufferedInputStream(final InputStream in) {
+        super(in);
+        buf = new byte[IOUtils.DEFAULT_BUFFER_SIZE];
+    }
+
+    /**
+     * Constructs a new {@code BufferedInputStream} on the {@link InputStream} 
{@code in}. The buffer size is specified by the parameter {@code size} and all
+     * reads are now filtered through this stream.
+     *
+     * @param in   the input stream the buffer reads from.
+     * @param size the size of buffer to allocate.
+     * @throws IllegalArgumentException if {@code size < 0}.
+     */
+    public UnsynchronizedBufferedInputStream(final InputStream in, final int 
size) {
+        super(in);
+        if (size <= 0) {
+            throw new IllegalArgumentException("Size must be > 0");
+        }
+        buf = new byte[size];
+    }
+
+    /**
+     * Returns the number of bytes that are available before this stream will 
block. This method returns the number of bytes available in the buffer plus 
those
+     * available in the source stream.
+     *
+     * @return the number of bytes available before blocking.
+     * @throws IOException if this stream is closed.
+     */
+    @Override
+    public int available() throws IOException {
+        final InputStream localIn = in; // 'in' could be invalidated by close()
+        if (buf == null || localIn == null) {
+            throw new IOException("Stream is closed");
+        }
+        return count - pos + localIn.available();
+    }
+
+    /**
+     * Closes this stream. The source stream is closed and any resources 
associated with it are released.
+     *
+     * @throws IOException if an error occurs while closing this stream.
+     */
+    @Override
+    public void close() throws IOException {
+        buf = null;
+        final InputStream localIn = in;
+        in = null;
+        if (localIn != null) {
+            localIn.close();
+        }
+    }
+
+    private int fillbuf(final InputStream localIn, byte[] localBuf) throws 
IOException {
+        if (markpos == IOUtils.EOF || pos - markpos >= marklimit) {
+            /* Mark position not set or exceeded readlimit */
+            final int result = localIn.read(localBuf);
+            if (result > 0) {
+                markpos = IOUtils.EOF;
+                pos = 0;
+                count = result;
+            }
+            return result;
+        }
+        if (markpos == 0 && marklimit > localBuf.length) {
+            /* Increase buffer size to accommodate the readlimit */
+            int newLength = localBuf.length * 2;
+            if (newLength > marklimit) {
+                newLength = marklimit;
+            }
+            final byte[] newbuf = new byte[newLength];
+            System.arraycopy(localBuf, 0, newbuf, 0, localBuf.length);
+            // Reassign buf, which will invalidate any local references
+            // FIXME: what if buf was null?
+            localBuf = buf = newbuf;
+        } else if (markpos > 0) {
+            System.arraycopy(localBuf, markpos, localBuf, 0, localBuf.length - 
markpos);
+        }
+        /* Set the new position and mark position */
+        pos -= markpos;
+        count = markpos = 0;
+        final int bytesread = localIn.read(localBuf, pos, localBuf.length - 
pos);
+        count = bytesread <= 0 ? pos : pos + bytesread;
+        return bytesread;
+    }
+
+    /**
+     * Sets a mark position in this stream. The parameter {@code readlimit} 
indicates how many bytes can be read before a mark is invalidated. Calling
+     * {@code reset()} will reposition the stream back to the marked position 
if {@code readlimit} has not been surpassed. The underlying buffer may be
+     * increased in size to allow {@code readlimit} number of bytes to be 
supported.
+     *
+     * @param readlimit the number of bytes that can be read before the mark 
is invalidated.
+     * @see #reset()
+     */
+    @Override
+    public void mark(final int readlimit) {
+        marklimit = readlimit;
+        markpos = pos;
+    }
+
+    /**
+     * Indicates whether {@code BufferedInputStream} supports the {@code 
mark()} and {@code reset()} methods.
+     *
+     * @return {@code true} for BufferedInputStreams.
+     * @see #mark(int)
+     * @see #reset()
+     */
+    @Override
+    public boolean markSupported() {
+        return true;
+    }
+
+    /**
+     * Reads a single byte from this stream and returns it as an integer in 
the range from 0 to 255. Returns -1 if the end of the source string has been
+     * reached. If the internal buffer does not contain any available bytes 
then it is filled from the source stream and the first byte is returned.
+     *
+     * @return the byte read or -1 if the end of the source stream has been 
reached.
+     * @throws IOException if this stream is closed or another IOException 
occurs.
+     */
+    @Override
+    public int read() throws IOException {
+        // Use local refs since buf and in may be invalidated by an
+        // unsynchronized close()
+        byte[] localBuf = buf;
+        final InputStream localIn = in;
+        if (localBuf == null || localIn == null) {
+            throw new IOException("Stream is closed");
+        }
+
+        /* Are there buffered bytes available? */
+        if (pos >= count && fillbuf(localIn, localBuf) == IOUtils.EOF) {
+            return IOUtils.EOF; /* no, fill buffer */
+        }
+        // localBuf may have been invalidated by fillbuf
+        if (localBuf != buf) {
+            localBuf = buf;
+            if (localBuf == null) {
+                throw new IOException("Stream is closed");
+            }
+        }
+
+        /* Did filling the buffer fail with -1 (EOF)? */
+        if (count - pos > 0) {
+            return localBuf[pos++] & 0xFF;
+        }
+        return IOUtils.EOF;
+    }
+
+    /**
+     * Reads at most {@code length} bytes from this stream and stores them in 
byte array {@code buffer} starting at offset {@code offset}. Returns the number 
of
+     * bytes actually read or -1 if no bytes were read and the end of the 
stream was encountered. If all the buffered bytes have been used, a mark has 
not been
+     * set and the requested number of bytes is larger than the receiver's 
buffer size, this implementation bypasses the buffer and simply places the 
results
+     * directly into {@code buffer}.
+     *
+     * @param buffer the byte array in which to store the bytes read.
+     * @param offset the initial position in {@code buffer} to store the bytes 
read from this stream.
+     * @param length the maximum number of bytes to store in {@code buffer}.
+     * @return the number of bytes actually read or -1 if end of stream.
+     * @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 the stream is already closed or 
another IOException occurs.
+     */
+    @Override
+    public int read(final byte[] buffer, int offset, final int length) throws 
IOException {
+        // Use local ref since buf may be invalidated by an unsynchronized
+        // close()
+        byte[] localBuf = buf;
+        if (localBuf == null) {
+            throw new IOException("Stream is closed");
+        }
+        // avoid int overflow
+        if (offset > buffer.length - length || offset < 0 || length < 0) {
+            throw new IndexOutOfBoundsException();
+        }
+        if (length == 0) {
+            return 0;
+        }
+        final InputStream localIn = in;
+        if (localIn == null) {
+            throw new IOException("Stream is closed");
+        }
+
+        int required;
+        if (pos < count) {
+            /* There are bytes available in the buffer. */
+            final int copylength = count - pos >= length ? length : count - 
pos;
+            System.arraycopy(localBuf, pos, buffer, offset, copylength);
+            pos += copylength;
+            if (copylength == length || localIn.available() == 0) {
+                return copylength;
+            }
+            offset += copylength;
+            required = length - copylength;
+        } else {
+            required = length;
+        }
+
+        while (true) {
+            final int read;
+            /*
+             * If we're not marked and the required size is greater than the 
buffer, simply read the bytes directly bypassing the buffer.
+             */
+            if (markpos == IOUtils.EOF && required >= localBuf.length) {
+                read = localIn.read(buffer, offset, required);
+                if (read == IOUtils.EOF) {
+                    return required == length ? IOUtils.EOF : length - 
required;
+                }
+            } else {
+                if (fillbuf(localIn, localBuf) == IOUtils.EOF) {
+                    return required == length ? IOUtils.EOF : length - 
required;
+                }
+                // localBuf may have been invalidated by fillbuf
+                if (localBuf != buf) {
+                    localBuf = buf;
+                    if (localBuf == null) {
+                        throw new IOException("Stream is closed");
+                    }
+                }
+
+                read = count - pos >= required ? required : count - pos;
+                System.arraycopy(localBuf, pos, buffer, offset, read);
+                pos += read;
+            }
+            required -= read;
+            if (required == 0) {
+                return length;
+            }
+            if (localIn.available() == 0) {
+                return length - required;
+            }
+            offset += read;
+        }
+    }
+
+    /**
+     * Resets this stream to the last marked location.
+     *
+     * @throws IOException if this stream is closed, no mark has been set or 
the mark is no longer valid because more than {@code readlimit} bytes have been
+     *                     read since setting the mark.
+     * @see #mark(int)
+     */
+    @Override
+    public void reset() throws IOException {
+        if (buf == null) {
+            throw new IOException("Stream is closed");
+        }
+        if (IOUtils.EOF == markpos) {
+            throw new IOException("Mark has been invalidated");
+        }
+        pos = markpos;
+    }
+
+    /**
+     * Skips {@code amount} number of bytes in this stream. Subsequent {@code 
read()}'s will not return these bytes unless {@code reset()} is used.
+     *
+     * @param amount the number of bytes to skip. {@code skip} does nothing 
and returns 0 if {@code amount} is less than zero.
+     * @return the number of bytes actually skipped.
+     * @throws IOException if this stream is closed or another IOException 
occurs.
+     */
+    @Override
+    public long skip(final long amount) throws IOException {
+        // Use local refs since buf and in may be invalidated by an
+        // unsynchronized close()
+        final byte[] localBuf = buf;
+        final InputStream localIn = in;
+        if (localBuf == null) {
+            throw new IOException("Stream is closed");
+        }
+        if (amount < 1) {
+            return 0;
+        }
+        if (localIn == null) {
+            throw new IOException("Stream is closed");
+        }
+
+        if (count - pos >= amount) {
+            pos += amount;
+            return amount;
+        }
+        long read = count - pos;
+        pos = count;
+
+        if (markpos != IOUtils.EOF && amount <= marklimit) {
+            if (fillbuf(localIn, localBuf) == IOUtils.EOF) {
+                return read;
+            }
+            if (count - pos >= amount - read) {
+                pos += amount - read;
+                return amount;
+            }
+            // Couldn't get all the bytes, skip what we read
+            read += count - pos;
+            pos = count;
+            return read;
+        }
+        return read + localIn.skip(amount - read);
+    }
+}
diff --git 
a/src/test/java/org/apache/commons/io/input/UnsynchronizedBufferedInputStreamTest.java
 
b/src/test/java/org/apache/commons/io/input/UnsynchronizedBufferedInputStreamTest.java
new file mode 100644
index 00000000..547f73c4
--- /dev/null
+++ 
b/src/test/java/org/apache/commons/io/input/UnsynchronizedBufferedInputStreamTest.java
@@ -0,0 +1,484 @@
+/*
+ *  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.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests {@link UnsynchronizedBufferedInputStream}.
+ * <p>
+ * Provenance: Apache Harmony and modified.
+ * </p>
+ */
+public class UnsynchronizedBufferedInputStreamTest {
+
+    private static final int BUFFER_SIZE = 4096;
+
+    static class MyUnsynchronizedBufferedInputStream extends 
UnsynchronizedBufferedInputStream {
+        static byte[] buf;
+
+        MyUnsynchronizedBufferedInputStream(final InputStream is) {
+            super(is);
+            buf = super.buf;
+        }
+
+        MyUnsynchronizedBufferedInputStream(final InputStream is, final int 
size) {
+            super(is, size);
+            buf = super.buf;
+        }
+    }
+
+    Path fileName;
+
+    private BufferedInputStream is;
+
+    private InputStream isFile;
+
+    byte[] ibuf = new byte[BUFFER_SIZE];
+
+    public static final String DATA = StringUtils.repeat("This is a test.", 
500);
+
+    /**
+     * Sets up the fixture, for example, open a network connection. This 
method is called before a test is executed.
+     *
+     * @throws IOException Thrown on test failure.
+     */
+    @BeforeEach
+    protected void setUp() throws IOException {
+        fileName = Files.createTempFile(getClass().getSimpleName(), ".tst");
+        Files.write(fileName, DATA.getBytes("UTF-8"));
+
+        isFile = Files.newInputStream(fileName);
+        is = new BufferedInputStream(isFile);
+    }
+
+    /**
+     * Tears down the fixture, for example, close a network connection. This 
method is called after a test is executed.
+     *
+     * @throws IOException Thrown on test failure.
+     */
+    @AfterEach
+    protected void tearDown() throws IOException {
+        IOUtils.closeQuietly(is);
+        Files.deleteIfExists(fileName);
+    }
+
+    /**
+     * Tests java.io.BufferedInputStream#available()
+     *
+     * @throws IOException Thrown on test failure.
+     */
+    @Test
+    public void test_available() throws IOException {
+        assertTrue(is.available() == DATA.length(), "Returned incorrect number 
of available bytes");
+
+        // Test that a closed stream throws an IOE for available()
+        final BufferedInputStream bis = new BufferedInputStream(new 
ByteArrayInputStream(new byte[] { 'H', 'e', 'l', 'l', 'o', ' ', 'W', 'o', 'r', 
'l', 'd' }));
+        final int available = bis.available();
+        bis.close();
+        assertTrue(available != 0);
+
+        assertThrows(IOException.class, () -> bis.available(), "Expected test 
to throw IOE.");
+    }
+
+    /**
+     * Tests java.io.BufferedInputStream#close()
+     *
+     * @throws IOException Thrown on test failure.
+     */
+    @Test
+    public void test_close() throws IOException {
+        new BufferedInputStream(isFile).close();
+
+        // regression for HARMONY-667
+        try (BufferedInputStream buf = new BufferedInputStream(null, 5)) {
+            // closes
+        }
+
+        try (InputStream in = new InputStream() {
+            Object lock = new Object();
+
+            @Override
+            public void close() {
+                synchronized (lock) {
+                    lock.notifyAll();
+                }
+            }
+
+            @Override
+            public int read() {
+                return 1;
+            }
+
+            @Override
+            public int read(final byte[] buf, final int offset, final int 
length) {
+                synchronized (lock) {
+                    try {
+                        lock.wait(3000);
+                    } catch (final InterruptedException e) {
+                        // Ignore
+                    }
+                }
+                return 1;
+            }
+        }) {
+            final BufferedInputStream bufin = new BufferedInputStream(in);
+            final Thread thread = new Thread(() -> {
+                try {
+                    Thread.sleep(1000);
+                    bufin.close();
+                } catch (final Exception e) {
+                    // Ignored
+                }
+            });
+            thread.start();
+            assertThrows(IOException.class, () -> bufin.read(new byte[100], 0, 
99), "Should throw IOException");
+        }
+    }
+
+    /*
+     * Tests java.io.BufferedInputStream(InputStream)
+     */
+    @Test
+    public void test_ConstructorLjava_io_InputStream() throws IOException {
+        try (BufferedInputStream str = new BufferedInputStream(null)) {
+            assertThrows(IOException.class, () -> str.read(), "Expected an 
IOException");
+        }
+    }
+
+    /*
+     * Tests java.io.BufferedInputStream(InputStream)
+     */
+    @Test
+    public void test_ConstructorLjava_io_InputStreamI() throws IOException {
+        try (BufferedInputStream str = new BufferedInputStream(null, 1)) {
+            assertThrows(IOException.class, () -> str.read(), "Expected an 
IOException");
+        }
+
+        // Test for method java.io.BufferedInputStream(java.io.InputStream, 
int)
+
+        // Create buffer with exact size of file
+        is = new BufferedInputStream(isFile, this.DATA.length());
+        // Ensure buffer gets filled by evaluating one read
+        is.read();
+        // Close underlying FileInputStream, all but 1 buffered bytes should
+        // still be available.
+        isFile.close();
+        // Read the remaining buffered characters, no IOException should
+        // occur.
+        is.skip(this.DATA.length() - 2);
+        is.read();
+        // is.read should now throw an exception because it will have to be 
filled.
+        assertThrows(IOException.class, () -> is.read());
+
+        // regression test for harmony-2407
+        new MyUnsynchronizedBufferedInputStream(null);
+        assertNotNull(MyUnsynchronizedBufferedInputStream.buf);
+        MyUnsynchronizedBufferedInputStream.buf = null;
+        new MyUnsynchronizedBufferedInputStream(null, 100);
+        assertNotNull(MyUnsynchronizedBufferedInputStream.buf);
+    }
+
+    /**
+     * Tests java.io.BufferedInputStream#mark(int)
+     *
+     * @throws IOException Thrown on test failure.
+     */
+    @Test
+    public void test_markI() throws IOException {
+        final byte[] buf1 = new byte[100];
+        final byte[] buf2 = new byte[100];
+        is.skip(3000);
+        is.mark(1000);
+        is.read(buf1, 0, buf1.length);
+        is.reset();
+        is.read(buf2, 0, buf2.length);
+        is.reset();
+        assertTrue(new String(buf1, 0, buf1.length).equals(new String(buf2, 0, 
buf2.length)), "Failed to mark correct position");
+
+        byte[] bytes = new byte[256];
+        for (int i = 0; i < 256; i++) {
+            bytes[i] = (byte) i;
+        }
+        InputStream in = new BufferedInputStream(new 
ByteArrayInputStream(bytes), 12);
+        in.skip(6);
+        in.mark(14);
+        in.read(new byte[14], 0, 14);
+        in.reset();
+        assertTrue(in.read() == 6 && in.read() == 7, "Wrong bytes");
+
+        in = new BufferedInputStream(new ByteArrayInputStream(bytes), 12);
+        in.skip(6);
+        in.mark(8);
+        in.skip(7);
+        in.reset();
+        assertTrue(in.read() == 6 && in.read() == 7, "Wrong bytes 2");
+
+        BufferedInputStream buf = new BufferedInputStream(new 
ByteArrayInputStream(new byte[] { 0, 1, 2, 3, 4 }), 2);
+        buf.mark(3);
+        bytes = new byte[3];
+        int result = buf.read(bytes);
+        assertEquals(3, result);
+        assertEquals(0, bytes[0], "Assert 0:");
+        assertEquals(1, bytes[1], "Assert 1:");
+        assertEquals(2, bytes[2], "Assert 2:");
+        assertEquals(3, buf.read(), "Assert 3:");
+
+        buf = new BufferedInputStream(new ByteArrayInputStream(new byte[] { 0, 
1, 2, 3, 4 }), 2);
+        buf.mark(3);
+        bytes = new byte[4];
+        result = buf.read(bytes);
+        assertEquals(4, result);
+        assertEquals(0, bytes[0], "Assert 4:");
+        assertEquals(1, bytes[1], "Assert 5:");
+        assertEquals(2, bytes[2], "Assert 6:");
+        assertEquals(3, bytes[3], "Assert 7:");
+        assertEquals(4, buf.read(), "Assert 8:");
+        assertEquals(-1, buf.read(), "Assert 9:");
+
+        buf = new BufferedInputStream(new ByteArrayInputStream(new byte[] { 0, 
1, 2, 3, 4 }), 2);
+        buf.mark(Integer.MAX_VALUE);
+        buf.read();
+        buf.close();
+    }
+
+    /**
+     * Tests java.io.BufferedInputStream#markSupported()
+     */
+    @Test
+    public void test_markSupported() {
+        assertTrue(is.markSupported(), "markSupported returned incorrect 
value");
+    }
+
+    /**
+     * Tests java.io.BufferedInputStream#read()
+     *
+     * @throws IOException Thrown on test failure.
+     */
+    @Test
+    public void test_read() throws IOException {
+        final InputStreamReader isr = new InputStreamReader(is);
+        final int c = isr.read();
+        assertTrue(c == DATA.charAt(0), "read returned incorrect char");
+
+        final byte[] bytes = new byte[256];
+        for (int i = 0; i < 256; i++) {
+            bytes[i] = (byte) i;
+        }
+        final InputStream in = new BufferedInputStream(new 
ByteArrayInputStream(bytes), 12);
+        assertEquals(0, in.read(), "Wrong initial byte"); // Fill the buffer
+        final byte[] buf = new byte[14];
+        in.read(buf, 0, 14); // Read greater than the buffer
+        assertTrue(new String(buf, 0, 14).equals(new String(bytes, 1, 14)), 
"Wrong block read data");
+        assertEquals(15, in.read(), "Wrong bytes"); // Check next byte
+    }
+
+    /**
+     * Tests java.io.BufferedInputStream#read(byte[], int, int)
+     *
+     * @throws IOException Thrown on test failure.
+     */
+    @Test
+    public void test_read$BII() throws IOException {
+        final byte[] buf1 = new byte[100];
+        is.skip(3000);
+        is.mark(1000);
+        is.read(buf1, 0, buf1.length);
+        assertTrue(new String(buf1, 0, 
buf1.length).equals(DATA.substring(3000, 3100)), "Failed to read correct data");
+
+        try (BufferedInputStream bufin = new BufferedInputStream(new 
InputStream() {
+            int size = 2, pos = 0;
+
+            byte[] contents = new byte[size];
+
+            @Override
+            public int available() {
+                return size - pos;
+            }
+
+            @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 byte[] 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 > available()) {
+                    toRead = available();
+                }
+                System.arraycopy(contents, pos, buf, off, toRead);
+                pos += toRead;
+                return toRead;
+            }
+        })) {
+            bufin.read();
+            final int result = bufin.read(new byte[2], 0, 2);
+            assertTrue(result == 1, () -> "Incorrect result: " + result);
+        }
+    }
+
+    /**
+     * Tests java.io.BufferedInputStream#read(byte[], int, int)
+     *
+     * @throws IOException Thrown on test failure.
+     */
+    @Test
+    public void test_read$BII_Exception() throws IOException {
+        final BufferedInputStream bis = new BufferedInputStream(null);
+        assertThrows(NullPointerException.class, () -> bis.read(null, -1, -1));
+
+        assertThrows(IndexOutOfBoundsException.class, () -> bis.read(new 
byte[0], -1, -1));
+        assertThrows(IndexOutOfBoundsException.class, () -> bis.read(new 
byte[0], 1, -1));
+        assertThrows(IndexOutOfBoundsException.class, () -> bis.read(new 
byte[0], 1, 1));
+
+        bis.close();
+
+        assertThrows(IOException.class, () -> bis.read(null, -1, -1));
+    }
+
+    /**
+     * Tests java.io.BufferedInputStream#reset()
+     *
+     * @throws IOException Thrown on test failure.
+     */
+    @Test
+    public void test_reset() throws IOException {
+        final byte[] buf1 = new byte[10];
+        final byte[] buf2 = new byte[10];
+        is.mark(2000);
+        is.read(buf1, 0, 10);
+        is.reset();
+        is.read(buf2, 0, 10);
+        is.reset();
+        assertTrue(new String(buf1, 0, buf1.length).equals(new String(buf2, 0, 
buf2.length)), "Reset failed");
+
+        final BufferedInputStream bIn = new BufferedInputStream(new 
ByteArrayInputStream("1234567890".getBytes()));
+        bIn.mark(10);
+        for (int i = 0; i < 11; i++) {
+            bIn.read();
+        }
+        bIn.reset();
+    }
+
+    /**
+     * Tests java.io.BufferedInputStream#reset()
+     *
+     * @throws IOException Thrown on test failure.
+     */
+    @Test
+    public void test_reset_Exception() throws IOException {
+        final BufferedInputStream bis = new BufferedInputStream(null);
+
+        // throws IOException with message "Mark has been invalidated"
+        assertThrows(IOException.class, () -> bis.reset());
+
+        // does not throw IOException
+        bis.mark(1);
+        bis.reset();
+
+        bis.close();
+
+        // throws IOException with message "stream is closed"
+        assertThrows(IOException.class, () -> bis.reset());
+    }
+
+    /**
+     * Tests java.io.BufferedInputStream#reset()
+     *
+     * @throws IOException Thrown on test failure.
+     */
+    @Test
+    public void test_reset_scenario1() throws IOException {
+        final byte[] input = "12345678900".getBytes();
+        final BufferedInputStream buffis = new BufferedInputStream(new 
ByteArrayInputStream(input));
+        buffis.read();
+        buffis.mark(5);
+        buffis.skip(5);
+        buffis.reset();
+    }
+
+    /**
+     * Tests java.io.BufferedInputStream#reset()
+     *
+     * @throws IOException Thrown on test failure.
+     */
+    @Test
+    public void test_reset_scenario2() throws IOException {
+        final byte[] input = "12345678900".getBytes();
+        final BufferedInputStream buffis = new BufferedInputStream(new 
ByteArrayInputStream(input));
+        buffis.mark(5);
+        buffis.skip(6);
+        buffis.reset();
+    }
+
+    /**
+     * Tests java.io.BufferedInputStream#skip(long)
+     *
+     * @throws IOException Thrown on test failure.
+     */
+    @Test
+    public void test_skip_NullInputStream() throws IOException {
+        try (BufferedInputStream buf = new BufferedInputStream(null, 5)) {
+            assertEquals(0, buf.skip(0));
+        }
+    }
+
+    /**
+     * Tests java.io.BufferedInputStream#skip(long)
+     *
+     * @throws IOException Thrown on test failure.
+     */
+    @Test
+    public void test_skipJ() throws IOException {
+        final byte[] buf1 = new byte[10];
+        is.mark(2000);
+        is.skip(1000);
+        is.read(buf1, 0, buf1.length);
+        is.reset();
+        assertTrue(new String(buf1, 0, 
buf1.length).equals(DATA.substring(1000, 1010)), "Failed to skip to correct 
position");
+
+        // regression for HARMONY-667
+        try (BufferedInputStream buf = new BufferedInputStream(null, 5)) {
+            assertThrows(IOException.class, () -> buf.skip(10));
+        }
+    }
+}

Reply via email to