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 1e7b2925a24e99d6cf092b5adae136bf055a001b
Author: Gary Gregory <garydgreg...@gmail.com>
AuthorDate: Wed Feb 1 09:10:51 2023 -0500

    [IO-786] Add UnsynchronizedFilterInputStream
---
 src/changes/changes.xml                            |   3 +
 .../io/input/UnsynchronizedFilterInputStream.java  | 175 +++++++++++++++++++++
 .../input/UnsynchronizedFilterInputStreamTest.java | 175 +++++++++++++++++++++
 3 files changed, 353 insertions(+)

diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 966d463a..412f94ab 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -457,6 +457,9 @@ The <action> type attribute can be add,update,fix,remove.
       <action dev="ggregory" type="add" due-to="DaGeRe, Gary Gregory">
         Add and use ThreadUtils.
       </action>
+      <action issue="IO-786" dev="ggregory" type="add" due-to="Gary Gregory">
+        Add UnsynchronizedFilterInputStream.
+      </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/UnsynchronizedFilterInputStream.java
 
b/src/main/java/org/apache/commons/io/input/UnsynchronizedFilterInputStream.java
new file mode 100644
index 00000000..b76c668e
--- /dev/null
+++ 
b/src/main/java/org/apache/commons/io/input/UnsynchronizedFilterInputStream.java
@@ -0,0 +1,175 @@
+/*
+ *  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.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * An unsynchronized version of {@link FilterInputStream}, not thread-safe.
+ * <p>
+ * Wraps an existing {@link InputStream} and performs some transformation on 
the input data while it is being read. Transformations can be anything from a
+ * simple byte-wise filtering input data to an on-the-fly compression or 
decompression of the underlying stream. Input streams that wrap another input 
stream
+ * and provide some additional functionality on top of it usually inherit from 
this class.
+ * </p>
+ * <p>
+ * Provenance: Apache Harmony and modified.
+ * </p>
+ *
+ * @see FilterInputStream
+ * @since 2.12.0
+ */
+//@NotThreadSafe
+public class UnsynchronizedFilterInputStream extends InputStream {
+
+    /**
+     * The source input stream that is filtered.
+     */
+    protected volatile InputStream in;
+
+    /**
+     * Constructs a new {@code FilterInputStream} with the specified input 
stream as source.
+     *
+     * @param in the non-null InputStream to filter reads on.
+     */
+    protected UnsynchronizedFilterInputStream(final InputStream in) {
+        this.in = in;
+    }
+
+    /**
+     * Returns the number of bytes that are available before this stream will 
block.
+     *
+     * @return the number of bytes available before blocking.
+     * @throws IOException if an error occurs in this stream.
+     */
+    @Override
+    public int available() throws IOException {
+        return in.available();
+    }
+
+    /**
+     * Closes this stream. This implementation closes the filtered stream.
+     *
+     * @throws IOException if an error occurs while closing this stream.
+     */
+    @Override
+    public void close() throws IOException {
+        in.close();
+    }
+
+    /**
+     * Sets a mark position in this stream. The parameter {@code readlimit} 
indicates how many bytes can be read before the mark is invalidated. Sending
+     * {@code reset()} will reposition this stream back to the marked 
position, provided that {@code readlimit} has not been surpassed.
+     * <p>
+     * This implementation sets a mark in the filtered stream.
+     *
+     * @param readlimit the number of bytes that can be read from this stream 
before the mark is invalidated.
+     * @see #markSupported()
+     * @see #reset()
+     */
+    @SuppressWarnings("sync-override") // by design.
+    @Override
+    public void mark(final int readlimit) {
+        in.mark(readlimit);
+    }
+
+    /**
+     * Indicates whether this stream supports {@code mark()} and {@code 
reset()}. This implementation returns whether or not the filtered stream 
supports
+     * marking.
+     *
+     * @return {@code true} if {@code mark()} and {@code reset()} are 
supported, {@code false} otherwise.
+     * @see #mark(int)
+     * @see #reset()
+     * @see #skip(long)
+     */
+    @Override
+    public boolean markSupported() {
+        return in.markSupported();
+    }
+
+    /**
+     * Reads a single byte from the filtered stream and returns it as an 
integer in the range from 0 to 255. Returns -1 if the end of this stream has 
been
+     * reached.
+     *
+     * @return the byte read or -1 if the end of the filtered stream has been 
reached.
+     * @throws IOException if the stream is closed or another IOException 
occurs.
+     */
+    @Override
+    public int read() throws IOException {
+        return in.read();
+    }
+
+    /**
+     * Reads bytes from this stream and stores them in the byte array {@code 
buffer}. Returns the number of bytes actually read or -1 if no bytes were read 
and
+     * the end of this stream was encountered. This implementation reads bytes 
from the filtered stream.
+     *
+     * @param buffer the byte array in which to store the read bytes.
+     * @return the number of bytes actually read or -1 if the end of the 
filtered stream has been reached while reading.
+     * @throws IOException if this stream is closed or another IOException 
occurs.
+     */
+    @Override
+    public int read(final byte[] buffer) throws IOException {
+        return read(buffer, 0, buffer.length);
+    }
+
+    /**
+     * Reads at most {@code count} bytes from this stream and stores them in 
the byte array {@code buffer} starting at {@code offset}. Returns the number of
+     * bytes actually read or -1 if no bytes have been read and the end of 
this stream has been reached. This implementation reads bytes from the filtered
+     * stream.
+     *
+     * @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 count  the maximum number of bytes to store in {@code buffer}.
+     * @return the number of bytes actually read or -1 if the end of the 
filtered stream has been reached while reading.
+     * @throws IOException if this stream is closed or another I/O error 
occurs.
+     */
+    @Override
+    public int read(final byte[] buffer, final int offset, final int count) 
throws IOException {
+        return in.read(buffer, offset, count);
+    }
+
+    /**
+     * Resets this stream to the last marked location. This implementation 
resets the target stream.
+     *
+     * @throws IOException if this stream is already 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)
+     * @see #markSupported()
+     */
+    @SuppressWarnings("sync-override") // by design.
+    @Override
+    public void reset() throws IOException {
+        in.reset();
+    }
+
+    /**
+     * Skips {@code count} number of bytes in this stream. Subsequent {@code 
read()}'s will not return these bytes unless {@code reset()} is used. This
+     * implementation skips {@code count} number of bytes in the filtered 
stream.
+     *
+     * @param count the number of bytes to skip.
+     * @return the number of bytes actually skipped.
+     * @throws IOException if this stream is closed or another IOException 
occurs.
+     * @see #mark(int)
+     * @see #reset()
+     */
+    @Override
+    public long skip(final long count) throws IOException {
+        return in.skip(count);
+    }
+}
diff --git 
a/src/test/java/org/apache/commons/io/input/UnsynchronizedFilterInputStreamTest.java
 
b/src/test/java/org/apache/commons/io/input/UnsynchronizedFilterInputStreamTest.java
new file mode 100644
index 00000000..005507d8
--- /dev/null
+++ 
b/src/test/java/org/apache/commons/io/input/UnsynchronizedFilterInputStreamTest.java
@@ -0,0 +1,175 @@
+package org.apache.commons.io.input;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/*
+ *  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.
+ */
+
+import java.io.IOException;
+import java.io.InputStream;
+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 UnsynchronizedFilterInputStream}.
+ * <p>
+ * Provenance: Apache Harmony and modified.
+ * </p>
+ */
+public class UnsynchronizedFilterInputStreamTest {
+
+    static class MyUnsynchronizedFilterInputStream extends 
UnsynchronizedFilterInputStream {
+        public MyUnsynchronizedFilterInputStream(final InputStream is) {
+            super(is);
+        }
+    }
+
+    private Path fileName;
+
+    private InputStream is;
+
+    byte[] ibuf = new byte[4096];
+
+    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.
+     */
+    @SuppressWarnings("resource") // See @AfterEach tearDown() method
+    @BeforeEach
+    protected void setUp() throws IOException {
+        fileName = Files.createTempFile(getClass().getSimpleName(), ".tst");
+        Files.write(fileName, DATA.getBytes("UTF-8"));
+        is = new 
MyUnsynchronizedFilterInputStream(Files.newInputStream(fileName));
+    }
+
+    /**
+     * 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.FilterInputStream#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");
+    }
+
+    /**
+     * Tests java.io.FilterInputStream#close()
+     *
+     * @throws IOException Thrown on test failure.
+     */
+    @Test
+    public void test_close() throws IOException {
+        is.close();
+        assertThrows(IOException.class, () -> is.read(), "Able to read from 
closed stream");
+    }
+
+    /**
+     * Tests java.io.FilterInputStream#mark(int)
+     */
+    @Test
+    public void test_markI() {
+        assertTrue(true, "Mark not supported by parent InputStream");
+    }
+
+    /**
+     * Tests java.io.FilterInputStream#markSupported()
+     */
+    @Test
+    public void test_markSupported() {
+        assertTrue(!is.markSupported(), "markSupported returned true");
+    }
+
+    /**
+     * Tests java.io.FilterInputStream#read()
+     *
+     * @throws IOException Thrown on test failure.
+     */
+    @Test
+    public void test_read() throws IOException {
+        final int c = is.read();
+        assertTrue(c == DATA.charAt(0), "read returned incorrect char");
+    }
+
+    /**
+     * Tests java.io.FilterInputStream#read(byte[])
+     *
+     * @throws IOException Thrown on test failure.
+     */
+    @Test
+    public void test_read$B() throws IOException {
+        final byte[] buf1 = new byte[100];
+        is.read(buf1);
+        assertTrue(new String(buf1, 0, buf1.length, 
"UTF-8").equals(DATA.substring(0, 100)), "Failed to read correct data");
+    }
+
+    /**
+     * Tests java.io.FilterInputStream#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, 
"UTF-8").equals(DATA.substring(3000, 3100)), "Failed to read correct data");
+    }
+
+    /**
+     * Tests java.io.FilterInputStream#reset()
+     */
+    @Test
+    public void test_reset() {
+        assertThrows(IOException.class, () -> is.reset(), "should throw 
IOException");
+
+    }
+
+    /**
+     * Tests java.io.FilterInputStream#skip(long)
+     *
+     * @throws IOException Thrown on test failure.
+     */
+    @Test
+    public void test_skipJ() throws IOException {
+        final byte[] buf1 = new byte[10];
+        is.skip(1000);
+        is.read(buf1, 0, buf1.length);
+        assertTrue(new String(buf1, 0, buf1.length, 
"UTF-8").equals(DATA.substring(1000, 1010)), "Failed to skip to correct 
position");
+    }
+}

Reply via email to