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 97ae01c  Add org.apache.commons.io.input.CircularInputStream.
97ae01c is described below

commit 97ae01c95837f50a2e9be34c370b271c4d8fc88b
Author: Gary Gregory <[email protected]>
AuthorDate: Wed Jul 1 10:21:12 2020 -0400

    Add org.apache.commons.io.input.CircularInputStream.
    
    [IO-674] InfiniteCircularInputStream is not infinite if its input buffer
    contains -1.
    
    [IO-675] InfiniteCircularInputStream throws a divide-by-zero exception
    when reading if its input buffer is size 0.
    
    Update version from 2.7.1-SNAPSHOT to 2.8-SNAPSHOT since we are adding a
    new public class.
---
 pom.xml                                            |  7 +-
 src/changes/changes.xml                            | 11 ++-
 .../commons/io/input/CircularInputStream.java      | 87 ++++++++++++++++++++++
 .../io/input/InfiniteCircularInputStream.java      | 30 +++-----
 ...treamTest.java => CircularInputStreamTest.java} | 77 +++++++++++++++----
 .../io/input/InfiniteCircularInputStreamTest.java  | 68 +++++++++++++----
 6 files changed, 227 insertions(+), 53 deletions(-)

diff --git a/pom.xml b/pom.xml
index 3503eac..a936fa4 100644
--- a/pom.xml
+++ b/pom.xml
@@ -24,7 +24,7 @@
   <modelVersion>4.0.0</modelVersion>
   <groupId>commons-io</groupId>
   <artifactId>commons-io</artifactId>
-  <version>2.7.1-SNAPSHOT</version>
+  <version>2.8-SNAPSHOT</version>
   <name>Apache Commons IO</name>
 
   <inceptionYear>2002</inceptionYear>
@@ -268,8 +268,8 @@ file comparators, endian transformation classes, and much 
more.
     <commons.componentid>io</commons.componentid>
     <commons.module.name>org.apache.commons.io</commons.module.name>
     <commons.rc.version>RC1</commons.rc.version>
-    <commons.bc.version>2.6</commons.bc.version>
-    <commons.release.version>2.7</commons.release.version>
+    <commons.bc.version>2.7</commons.bc.version>
+    <commons.release.version>2.8</commons.release.version>
     <commons.release.desc>(requires Java 8)</commons.release.desc>
     <commons.jira.id>IO</commons.jira.id>
     <commons.jira.pid>12310477</commons.jira.pid>
@@ -296,7 +296,6 @@ file comparators, endian transformation classes, and much 
more.
     <commons.japicmp.version>0.14.3</commons.japicmp.version>
     <japicmp.skip>false</japicmp.skip>
     <jacoco.skip>${env.JACOCO_SKIP}</jacoco.skip>
-    <commons.bc.version>2.6</commons.bc.version>
     <commons.release.isDistModule>true</commons.release.isDistModule>
     <commons.releaseManagerName>Gary Gregory</commons.releaseManagerName>    
     <commons.releaseManagerKey>86fdc7e2a11262cb</commons.releaseManagerKey>
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index b7eee83..09f345f 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -46,7 +46,7 @@ The <action> type attribute can be add,update,fix,remove.
 
   <body>
     <!-- The release date is the date RC is cut -->
-    <release version="2.7.1" date="2020-MM-DD" description="Java 8 required.">
+    <release version="2.8" date="2020-MM-DD" description="Java 8 required.">
       <action issue="IO-669" dev="ggregory" type="fix" due-to="XenoAmess, Gary 
Gregory">
         Fix code smells; fix typos #115.
       </action>
@@ -56,6 +56,15 @@ The <action> type attribute can be add,update,fix,remove.
       <action issue="IO-673" dev="ggregory" type="fix" due-to="Jerome Wolff">
         Make some simplifications #121.
       </action>
+      <action dev="ggregory" type="fix" due-to="Gary Gregory">
+        Add org.apache.commons.io.input.CircularInputStream.
+      </action>
+      <action issue="IO-674" dev="ggregory" type="fix" due-to="Gary Gregory">
+        InfiniteCircularInputStream is not infinite if its input buffer 
contains -1.
+      </action>
+      <action issue="IO-675" dev="ggregory" type="fix" due-to="Gary Gregory">
+        InfiniteCircularInputStream throws a divide-by-zero exception when 
reading if its input buffer is size 0.
+      </action>
     </release>
     <!-- The release date is the date RC is cut -->
     <release version="2.7" date="2020-05-24" description="Java 8 required.">
diff --git a/src/main/java/org/apache/commons/io/input/CircularInputStream.java 
b/src/main/java/org/apache/commons/io/input/CircularInputStream.java
new file mode 100644
index 0000000..5166b38
--- /dev/null
+++ b/src/main/java/org/apache/commons/io/input/CircularInputStream.java
@@ -0,0 +1,87 @@
+/*
+ * 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.IOException;
+import java.io.InputStream;
+import java.util.Objects;
+
+import org.apache.commons.io.IOUtils;
+
+/**
+ *
+ * An {@link InputStream} that repeats provided bytes for given target byte 
count.
+ * <p>
+ * Closing this input stream has no effect. The methods in this class can be 
called after the stream has been closed
+ * without generating an {@link IOException}.
+ * </p>
+ *
+ * @see InfiniteCircularInputStream
+ * @since 2.8
+ */
+public class CircularInputStream extends InputStream {
+
+    /**
+     * Throws an {@link IllegalArgumentException} if the input contains -1.
+     *
+     * @param repeatContent input to validate.
+     * @return the input.
+     */
+    private static byte[] validate(final byte[] repeatContent) {
+        Objects.requireNonNull(repeatContent, "repeatContent");
+        for (final byte b : repeatContent) {
+            if (b == IOUtils.EOF) {
+                throw new IllegalArgumentException("repeatContent contains the 
end-of-stream marker " + IOUtils.EOF);
+            }
+        }
+        return repeatContent;
+    }
+    private long byteCount;
+    private int position = -1;
+    private final byte[] repeatedContent;
+    private final long targetByteCount;
+
+    /**
+     * Creates an instance from the specified array of bytes.
+     *
+     * @param repeatContent Input buffer to be repeated this buffer is not 
copied.
+     * @param targetByteCount How many bytes the read. A negative number means 
an infinite target count.
+     */
+    public CircularInputStream(final byte[] repeatContent, final long 
targetByteCount) {
+        this.repeatedContent = validate(repeatContent);
+        if (repeatContent.length == 0) {
+            throw new IllegalArgumentException("repeatContent is empty.");
+        }
+        this.targetByteCount = targetByteCount;
+    }
+
+    @Override
+    public int read() {
+        if (repeatedContent.length == 0) {
+            return IOUtils.EOF;
+        }
+        if (targetByteCount >= 0) {
+            if (byteCount == targetByteCount) {
+                return IOUtils.EOF;
+            }
+            byteCount++;
+        }
+        position = (position + 1) % repeatedContent.length;
+        return repeatedContent[position] & 0xff;
+    }
+
+}
diff --git 
a/src/main/java/org/apache/commons/io/input/InfiniteCircularInputStream.java 
b/src/main/java/org/apache/commons/io/input/InfiniteCircularInputStream.java
index 70a564c..0514c7e 100644
--- a/src/main/java/org/apache/commons/io/input/InfiniteCircularInputStream.java
+++ b/src/main/java/org/apache/commons/io/input/InfiniteCircularInputStream.java
@@ -16,37 +16,29 @@
  */
 package org.apache.commons.io.input;
 
+import java.io.IOException;
 import java.io.InputStream;
 
 /**
  *
- * An {@link InputStream} that infinitely repeats provided bytes.
+ * An {@link InputStream} that infinitely repeats the provided bytes.
  * <p>
- * Closing a <code>InfiniteCircularInputStream</code> has no effect. The 
methods in
- * this class can be called after the stream has been closed without generating
- * an <code>IOException</code>.
+ * Closing this input stream has no effect. The methods in this class can be 
called after the stream has been closed
+ * without generating an {@link IOException}.
  * </p>
+ *
  * @since 2.6
  */
-public class InfiniteCircularInputStream extends InputStream {
-
-    final private byte[] repeatedContent;
-    private int position = -1;
+public class InfiniteCircularInputStream extends CircularInputStream {
 
     /**
-     * Creates a InfiniteCircularStream from the specified array of chars.
+     * Creates an instance from the specified array of bytes.
      *
-     * @param repeatedContent
-     *            Input buffer to be repeated (not copied)
+     * @param repeatContent Input buffer to be repeated this buffer is not 
copied.
      */
-    public InfiniteCircularInputStream(final byte[] repeatedContent) {
-        this.repeatedContent = repeatedContent;
-    }
-
-    @Override
-    public int read() {
-        position = (position + 1) % repeatedContent.length;
-        return repeatedContent[position] & 0xff;
+    public InfiniteCircularInputStream(final byte[] repeatContent) {
+        // A negative number means an infinite target count.
+        super(repeatContent, -1);
     }
 
 }
diff --git 
a/src/test/java/org/apache/commons/io/input/InfiniteCircularInputStreamTest.java
 b/src/test/java/org/apache/commons/io/input/CircularInputStreamTest.java
similarity index 53%
copy from 
src/test/java/org/apache/commons/io/input/InfiniteCircularInputStreamTest.java
copy to src/test/java/org/apache/commons/io/input/CircularInputStreamTest.java
index 0e63ecb..431f353 100644
--- 
a/src/test/java/org/apache/commons/io/input/InfiniteCircularInputStreamTest.java
+++ b/src/test/java/org/apache/commons/io/input/CircularInputStreamTest.java
@@ -18,17 +18,69 @@ package org.apache.commons.io.input;
 
 import static org.junit.jupiter.api.Assertions.assertArrayEquals;
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
 
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.Arrays;
 
+import org.apache.commons.io.IOUtils;
 import org.junit.jupiter.api.Test;
 
-public class InfiniteCircularInputStreamTest {
+/**
+ * Tests {@link CircularInputStream}.
+ */
+public class CircularInputStreamTest {
+
+    private void assertStreamOutput(final byte[] toCycle, final byte[] 
expected) throws IOException {
+        final byte[] actual = new byte[expected.length];
+
+        try (InputStream infStream = createInputStream(toCycle, -1)) {
+            final int actualReadBytes = infStream.read(actual);
+
+            assertArrayEquals(expected, actual);
+            assertEquals(expected.length, actualReadBytes);
+        }
+    }
+
+    private InputStream createInputStream(final byte[] repeatContent, final 
long targetByteCount) {
+        return new CircularInputStream(repeatContent, targetByteCount);
+    }
+
+    @Test
+    public void testContainsEofInputSize0() {
+        assertThrows(IllegalArgumentException.class, () -> 
createInputStream(new byte[] { -1 }, 0));
+    }
+
+    @Test
+    public void testCount0() throws IOException {
+        try (InputStream in = createInputStream(new byte[] { 1, 2 }, 0)) {
+            assertEquals(IOUtils.EOF, in.read());
+        }
+    }
+
+    @Test
+    public void testCount0InputSize0() {
+        assertThrows(IllegalArgumentException.class, () -> 
createInputStream(new byte[] {}, 0));
+    }
+
+    @Test
+    public void testCount0InputSize1() throws IOException {
+        try (InputStream in = createInputStream(new byte[] { 1 }, 0)) {
+            assertEquals(IOUtils.EOF, in.read());
+        }
+    }
 
     @Test
-    public void shouldCycleBytes() throws IOException {
+    public void testCount1InputSize1() throws IOException {
+        try (InputStream in = createInputStream(new byte[] { 1 }, 1)) {
+            assertEquals(1, in.read());
+            assertEquals(IOUtils.EOF, in.read());
+        }
+    }
+
+    @Test
+    public void testCycleBytes() throws IOException {
         final byte[] input = new byte[] { 1, 2 };
         final byte[] expected = new byte[] { 1, 2, 1, 2, 1 };
 
@@ -36,12 +88,18 @@ public class InfiniteCircularInputStreamTest {
     }
 
     @Test
-    public void shouldHandleWholeRangeOfBytes() throws IOException {
+    public void testNullInputSize0() {
+        assertThrows(NullPointerException.class, () -> createInputStream(null, 
0));
+    }
+
+    @Test
+    public void testWholeRangeOfBytes() throws IOException {
         final int size = Byte.MAX_VALUE - Byte.MIN_VALUE + 1;
         final byte[] contentToCycle = new byte[size];
         byte value = Byte.MIN_VALUE;
         for (int i = 0; i < contentToCycle.length; i++) {
-            contentToCycle[i] = value++;
+            contentToCycle[i] = value == IOUtils.EOF ? 0 : value;
+            value++;
         }
 
         final byte[] expectedOutput = Arrays.copyOf(contentToCycle, size);
@@ -49,15 +107,4 @@ public class InfiniteCircularInputStreamTest {
         assertStreamOutput(contentToCycle, expectedOutput);
     }
 
-    private void assertStreamOutput(final byte[] toCycle, final byte[] 
expected) throws IOException {
-        final byte[] actual = new byte[expected.length];
-
-        try (InputStream infStream = new InfiniteCircularInputStream(toCycle)) 
{
-            final int actualReadBytes = infStream.read(actual);
-
-            assertArrayEquals(expected, actual);
-            assertEquals(expected.length, actualReadBytes);
-        }
-    }
-
 }
diff --git 
a/src/test/java/org/apache/commons/io/input/InfiniteCircularInputStreamTest.java
 
b/src/test/java/org/apache/commons/io/input/InfiniteCircularInputStreamTest.java
index 0e63ecb..6565855 100644
--- 
a/src/test/java/org/apache/commons/io/input/InfiniteCircularInputStreamTest.java
+++ 
b/src/test/java/org/apache/commons/io/input/InfiniteCircularInputStreamTest.java
@@ -18,17 +18,62 @@ package org.apache.commons.io.input;
 
 import static org.junit.jupiter.api.Assertions.assertArrayEquals;
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
 
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.Arrays;
 
+import org.apache.commons.io.IOUtils;
 import org.junit.jupiter.api.Test;
 
+/**
+ * Tests {@link InfiniteCircularInputStream}.
+ */
 public class InfiniteCircularInputStreamTest {
 
+    private void assertStreamOutput(final byte[] toCycle, final byte[] 
expected) throws IOException {
+        final byte[] actual = new byte[expected.length];
+
+        try (InputStream infStream = new InfiniteCircularInputStream(toCycle)) 
{
+            final int actualReadBytes = infStream.read(actual);
+
+            assertArrayEquals(expected, actual);
+            assertEquals(expected.length, actualReadBytes);
+        }
+    }
+
+    private InputStream createInputStream(final byte[] repeatContent) {
+        return new InfiniteCircularInputStream(repeatContent);
+    }
+
+    @Test
+    public void testContainsEofInputSize0() {
+        assertThrows(IllegalArgumentException.class, () -> 
createInputStream(new byte[] { -1 }));
+    }
+
+    @Test
+    public void testCount0InputSize0() {
+        assertThrows(IllegalArgumentException.class, () -> 
createInputStream(new byte[] {}));
+    }
+
+    @Test
+    public void testCount0InputSize1() throws IOException {
+        try (InputStream in = createInputStream(new byte[] { 1 })) {
+            // empty
+        }
+    }
+
     @Test
-    public void shouldCycleBytes() throws IOException {
+    public void testCount1InputSize1() throws IOException {
+        try (InputStream in = createInputStream(new byte[] { 1 })) {
+            assertEquals(1, in.read());
+            assertEquals(1, in.read());
+        }
+    }
+
+    @Test
+    public void testCycleBytes() throws IOException {
         final byte[] input = new byte[] { 1, 2 };
         final byte[] expected = new byte[] { 1, 2, 1, 2, 1 };
 
@@ -36,12 +81,18 @@ public class InfiniteCircularInputStreamTest {
     }
 
     @Test
-    public void shouldHandleWholeRangeOfBytes() throws IOException {
+    public void testNullInputSize0() {
+        assertThrows(NullPointerException.class, () -> 
createInputStream(null));
+    }
+
+    @Test
+    public void testWholeRangeOfBytes() throws IOException {
         final int size = Byte.MAX_VALUE - Byte.MIN_VALUE + 1;
         final byte[] contentToCycle = new byte[size];
         byte value = Byte.MIN_VALUE;
         for (int i = 0; i < contentToCycle.length; i++) {
-            contentToCycle[i] = value++;
+            contentToCycle[i] = value == IOUtils.EOF ? 0 : value;
+            value++;
         }
 
         final byte[] expectedOutput = Arrays.copyOf(contentToCycle, size);
@@ -49,15 +100,4 @@ public class InfiniteCircularInputStreamTest {
         assertStreamOutput(contentToCycle, expectedOutput);
     }
 
-    private void assertStreamOutput(final byte[] toCycle, final byte[] 
expected) throws IOException {
-        final byte[] actual = new byte[expected.length];
-
-        try (InputStream infStream = new InfiniteCircularInputStream(toCycle)) 
{
-            final int actualReadBytes = infStream.read(actual);
-
-            assertArrayEquals(expected, actual);
-            assertEquals(expected.length, actualReadBytes);
-        }
-    }
-
 }

Reply via email to