You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by gg...@apache.org on 2020/04/28 21:49:39 UTC

[commons-io] branch master updated: Support sub sequences in CharSequenceReader (#91)

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 39e3ea7  Support sub sequences in CharSequenceReader (#91)
39e3ea7 is described below

commit 39e3ea7fc0bb8d28b57c0213973ef4971fdeb265
Author: Rob Spoor <ro...@users.noreply.github.com>
AuthorDate: Tue Apr 28 23:49:28 2020 +0200

    Support sub sequences in CharSequenceReader (#91)
    
    * Added support for sub sequences in CharSequenceReader
    
    * Added missing Javadoc. Added entry to changes.xml.
    
    * Added missing @since tags.
    
    * [IO-619] Fixed issues reported in merge request #91
    
    * [IO-619] Use assertThrows instead of try-fail-catch
    
    * [IO-619] Fixed issues reported in merge request #91
---
 src/changes/changes.xml                            |   5 +-
 .../commons/io/input/CharSequenceReader.java       | 133 +++++++++++++++++++--
 .../commons/io/input/CharSequenceReaderTest.java   | 121 +++++++++++++++++++
 .../apache/commons/io/input/CharSequenceReader.bin | Bin 0 -> 139 bytes
 4 files changed, 247 insertions(+), 12 deletions(-)

diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index b3b1b8b..9266296 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -128,9 +128,12 @@ The <action> type attribute can be add,update,fix,remove.
       <action issue="IO-617" dev="ggregory" type="add" due-to="Rob Spoor, Gary Gregory">
         Add class CloseShieldWriter. #83.
       </action>
-      <action issue="IO-617" dev="ggregory" type="add" due-to="Rob Spoor">
+      <action issue="IO-618" dev="ggregory" type="add" due-to="Rob Spoor">
         Add classes Added TaggedReader, ClosedReader and BrokenReader. #85.
       </action>
+      <action issue="IO-619" dev="ggregory" type="add" due-to="Rob Spoor">
+        Support sub sequences in CharSequenceReader. #91.
+      </action>
       <action issue="IO-625" dev="ggregory" type="fix" due-to="Mikko Maunu">
         Corrected misleading exception message for FileUtils.copyDirectoryToDirectory.
       </action>
diff --git a/src/main/java/org/apache/commons/io/input/CharSequenceReader.java b/src/main/java/org/apache/commons/io/input/CharSequenceReader.java
index fcac3be..a2d845f 100644
--- a/src/main/java/org/apache/commons/io/input/CharSequenceReader.java
+++ b/src/main/java/org/apache/commons/io/input/CharSequenceReader.java
@@ -27,6 +27,7 @@ import java.util.Objects;
  * StringBuilder or CharBuffer.
  * <p>
  * <strong>Note:</strong> Supports {@link #mark(int)} and {@link #reset()}.
+ * </p>
  *
  * @since 1.4
  */
@@ -38,12 +39,121 @@ public class CharSequenceReader extends Reader implements Serializable {
     private int mark;
 
     /**
-     * Construct a new instance with the specified character sequence.
+     * The start index in the character sequence, inclusive.
+     * <p>
+     * When de-serializing a CharSequenceReader that was serialized before
+     * this fields was added, this field will be initialized to 0, which
+     * gives the same behavior as before: start reading from the start.
+     * </p>
+     *
+     * @see #start()
+     * @since 2.7
+     */
+    private final int start;
+
+    /**
+     * The end index in the character sequence, exclusive.
+     * <p>
+     * When de-serializing a CharSequenceReader that was serialized before
+     * this fields was added, this field will be initialized to {@code null},
+     * which gives the same behavior as before: stop reading at the
+     * CharSequence's length.
+     * If this field was an int instead, it would be initialized to 0 when the
+     * CharSequenceReader is de-serialized, causing it to not return any
+     * characters at all.
+     * </p>
+     *
+     * @see #end()
+     * @since 2.7
+     */
+    private final Integer end;
+
+    /**
+     * Constructs a new instance with the specified character sequence.
      *
      * @param charSequence The character sequence, may be {@code null}
      */
     public CharSequenceReader(final CharSequence charSequence) {
+        this(charSequence, 0);
+    }
+
+    /**
+     * Constructs a new instance with a portion of the specified character sequence.
+     * <p>
+     * The start index is not strictly enforced to be within the bounds of the
+     * character sequence. This allows the character sequence to grow or shrink
+     * in size without risking any {@link IndexOutOfBoundsException} to be thrown.
+     * Instead, if the character sequence grows smaller than the start index, this
+     * instance will act as if all characters have been read.
+     * </p>
+     *
+     * @param charSequence The character sequence, may be {@code null}
+     * @param start The start index in the character sequence, inclusive
+     * @throws IllegalArgumentException if the start index is negative
+     * @since 2.7
+     */
+    public CharSequenceReader(final CharSequence charSequence, final int start) {
+        this(charSequence, start, Integer.MAX_VALUE);
+    }
+
+    /**
+     * Constructs a new instance with a portion of the specified character sequence.
+     * <p>
+     * The start and end indexes are not strictly enforced to be within the bounds
+     * of the character sequence. This allows the character sequence to grow or shrink
+     * in size without risking any {@link IndexOutOfBoundsException} to be thrown.
+     * Instead, if the character sequence grows smaller than the start index, this
+     * instance will act as if all characters have been read; if the character sequence
+     * grows smaller than the end, this instance will use the actual character sequence
+     * length.
+     * </p>
+     *
+     * @param charSequence The character sequence, may be {@code null}
+     * @param start The start index in the character sequence, inclusive
+     * @param end The end index in the character sequence, exclusive
+     * @throws IllegalArgumentException if the start index is negative, or if the end index is smaller than the start index
+     * @since 2.7
+     */
+    public CharSequenceReader(final CharSequence charSequence, final int start, final int end) {
+        if (start < 0) {
+            throw new IllegalArgumentException(
+                    "Start index is less than zero: " + start);
+        }
+        if (end < start) {
+            throw new IllegalArgumentException(
+                    "End index is less than start " + start + ": " + end);
+        }
+        // Don't check the start and end indexes against the CharSequence,
+        // to let it grow and shrink without breaking existing behavior.
+
         this.charSequence = charSequence != null ? charSequence : "";
+        this.start = start;
+        this.end = end;
+
+        this.idx = start;
+        this.mark = start;
+    }
+
+    /**
+     * Returns the index in the character sequence to start reading from, taking into account its length.
+     *
+     * @return The start index in the character sequence (inclusive).
+     */
+    private int start() {
+        return Math.min(charSequence.length(), start);
+    }
+
+    /**
+     * Returns the index in the character sequence to end reading at, taking into account its length.
+     *
+     * @return The end index in the character sequence (exclusive).
+     */
+    private int end() {
+        /*
+         * end == null for de-serialized instances that were serialized before start and end were added.
+         * Use Integer.MAX_VALUE to get the same behavior as before - use the entire CharSequence.
+         */
+        return Math.min(charSequence.length(), end == null ? Integer.MAX_VALUE : end);
     }
 
     /**
@@ -51,8 +161,8 @@ public class CharSequenceReader extends Reader implements Serializable {
      */
     @Override
     public void close() {
-        idx = 0;
-        mark = 0;
+        idx = start;
+        mark = start;
     }
 
     /**
@@ -83,7 +193,7 @@ public class CharSequenceReader extends Reader implements Serializable {
      */
     @Override
     public int read() {
-        if (idx >= charSequence.length()) {
+        if (idx >= end()) {
             return EOF;
         }
         return charSequence.charAt(idx++);
@@ -100,7 +210,7 @@ public class CharSequenceReader extends Reader implements Serializable {
      */
     @Override
     public int read(final char[] array, final int offset, final int length) {
-        if (idx >= charSequence.length()) {
+        if (idx >= end()) {
             return EOF;
         }
         Objects.requireNonNull(array, "array");
@@ -110,19 +220,19 @@ public class CharSequenceReader extends Reader implements Serializable {
         }
 
         if (charSequence instanceof String) {
-            final int count = Math.min(length, charSequence.length() - idx);
+            final int count = Math.min(length, end() - idx);
             ((String) charSequence).getChars(idx, idx + count, array, offset);
             idx += count;
             return count;
         }
         if (charSequence instanceof StringBuilder) {
-            final int count = Math.min(length, charSequence.length() - idx);
+            final int count = Math.min(length, end() - idx);
             ((StringBuilder) charSequence).getChars(idx, idx + count, array, offset);
             idx += count;
             return count;
         }
         if (charSequence instanceof StringBuffer) {
-            final int count = Math.min(length, charSequence.length() - idx);
+            final int count = Math.min(length, end() - idx);
             ((StringBuffer) charSequence).getChars(idx, idx + count, array, offset);
             idx += count;
             return count;
@@ -161,10 +271,10 @@ public class CharSequenceReader extends Reader implements Serializable {
             throw new IllegalArgumentException(
                     "Number of characters to skip is less than zero: " + n);
         }
-        if (idx >= charSequence.length()) {
+        if (idx >= end()) {
             return EOF;
         }
-        final int dest = (int)Math.min(charSequence.length(), idx + n);
+        final int dest = (int)Math.min(end(), idx + n);
         final int count = dest - idx;
         idx = dest;
         return count;
@@ -178,6 +288,7 @@ public class CharSequenceReader extends Reader implements Serializable {
      */
     @Override
     public String toString() {
-        return charSequence.toString();
+        CharSequence subSequence = charSequence.subSequence(start(), end());
+        return subSequence.toString();
     }
 }
diff --git a/src/test/java/org/apache/commons/io/input/CharSequenceReaderTest.java b/src/test/java/org/apache/commons/io/input/CharSequenceReaderTest.java
index 12873c6..311e809 100644
--- a/src/test/java/org/apache/commons/io/input/CharSequenceReaderTest.java
+++ b/src/test/java/org/apache/commons/io/input/CharSequenceReaderTest.java
@@ -17,11 +17,17 @@
 package org.apache.commons.io.input;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
 import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
 import java.io.Reader;
 import java.nio.CharBuffer;
+import java.util.Arrays;
 
 import org.junit.jupiter.api.Test;
 
@@ -38,6 +44,11 @@ public class CharSequenceReaderTest {
         checkRead(reader, "Foo");
         reader.close();
         checkRead(reader, "Foo");
+
+        final Reader subReader = new CharSequenceReader("xFooBarx", 1, 7);
+        checkRead(subReader, "Foo");
+        subReader.close();
+        checkRead(subReader, "Foo");
     }
 
     @Test
@@ -60,6 +71,17 @@ public class CharSequenceReaderTest {
             reader.reset();
             checkRead(reader, "Foo");
         }
+        try (final Reader subReader = new CharSequenceReader("xFooBarx", 1, 7)) {
+            checkRead(subReader, "Foo");
+            subReader.mark(0);
+            checkRead(subReader, "Bar");
+            subReader.reset();
+            checkRead(subReader, "Bar");
+            subReader.close();
+            checkRead(subReader, "Foo");
+            subReader.reset();
+            checkRead(subReader, "Foo");
+        }
     }
 
     @Test
@@ -75,6 +97,18 @@ public class CharSequenceReaderTest {
         reader.close();
         assertEquals(6, reader.skip(20));
         assertEquals(-1, reader.read());
+
+        final Reader subReader = new CharSequenceReader("xFooBarx", 1, 7);
+        assertEquals(3, subReader.skip(3));
+        checkRead(subReader, "Bar");
+        assertEquals(-1, subReader.skip(3));
+        subReader.reset();
+        assertEquals(2, subReader.skip(2));
+        assertEquals(4, subReader.skip(10));
+        assertEquals(-1, subReader.skip(1));
+        subReader.close();
+        assertEquals(6, subReader.skip(20));
+        assertEquals(-1, subReader.read());
     }
 
     @Test
@@ -94,6 +128,12 @@ public class CharSequenceReaderTest {
             assertEquals(-1, reader.read());
             assertEquals(-1, reader.read());
         }
+        try (final Reader reader = new CharSequenceReader(charSequence, 1, 5)) {
+            assertEquals('o', reader.read());
+            assertEquals('o', reader.read());
+            assertEquals(-1, reader.read());
+            assertEquals(-1, reader.read());
+        }
     }
 
     @Test
@@ -118,6 +158,18 @@ public class CharSequenceReaderTest {
             checkArray(new char[] { 'r', NONE, NONE }, chars);
             assertEquals(-1, reader.read(chars));
         }
+        try (final Reader reader = new CharSequenceReader(charSequence, 1, 5)) {
+            char[] chars = new char[2];
+            assertEquals(2, reader.read(chars));
+            checkArray(new char[] { 'o', 'o' }, chars);
+            chars = new char[3];
+            assertEquals(2, reader.read(chars));
+            checkArray(new char[] { 'B', 'a', NONE }, chars);
+            chars = new char[3];
+            assertEquals(-1, reader.read(chars));
+            checkArray(new char[] { NONE, NONE, NONE }, chars);
+            assertEquals(-1, reader.read(chars));
+        }
     }
 
     @Test
@@ -138,6 +190,14 @@ public class CharSequenceReaderTest {
             checkArray(new char[] { 'B', 'a', 'r', 'F', 'o', 'o', NONE }, chars);
             assertEquals(-1, reader.read(chars));
         }
+        Arrays.fill(chars, NONE);
+        try (final Reader reader = new CharSequenceReader(charSequence, 1, 5)) {
+            assertEquals(2, reader.read(chars, 3, 2));
+            checkArray(new char[] { NONE, NONE, NONE, 'o', 'o', NONE }, chars);
+            assertEquals(2, reader.read(chars, 0, 3));
+            checkArray(new char[] { 'B', 'a', NONE, 'o', 'o', NONE }, chars);
+            assertEquals(-1, reader.read(chars));
+        }
     }
 
     private void checkRead(final Reader reader, final String expected) throws IOException {
@@ -151,4 +211,65 @@ public class CharSequenceReaderTest {
             assertEquals(expected[i], actual[i], "Compare[" +i + "]");
         }
     }
+
+    @Test
+    public void testConstructor() {
+        assertThrows(IllegalArgumentException.class, () -> new CharSequenceReader("FooBar", -1, 6),
+                "Expected exception not thrown for negative start.");
+        assertThrows(IllegalArgumentException.class, () -> new CharSequenceReader("FooBar", 1, 0),
+                "Expected exception not thrown for end before start.");
+    }
+
+    @Test
+    public void testToString() {
+        assertEquals("FooBar", new CharSequenceReader("FooBar").toString());
+        assertEquals("FooBar", new CharSequenceReader("xFooBarx", 1, 7).toString());
+    }
+
+    @Test
+    public void testSerialization() throws IOException, ClassNotFoundException {
+        /*
+         * File CharSequenceReader.bin contains a CharSequenceReader that was serialized before
+         * the start and end fields were added. Its CharSequence is "FooBar".
+         * This part of the test will test that adding the fields does not break any existing
+         * serialized CharSequenceReaders.
+         */
+        try (ObjectInputStream ois = new ObjectInputStream(getClass().getResourceAsStream("CharSequenceReader.bin"))) {
+            final CharSequenceReader reader = (CharSequenceReader) ois.readObject();
+            assertEquals('F', reader.read());
+            assertEquals('o', reader.read());
+            assertEquals('o', reader.read());
+            assertEquals('B', reader.read());
+            assertEquals('a', reader.read());
+            assertEquals('r', reader.read());
+            assertEquals(-1, reader.read());
+            assertEquals(-1, reader.read());
+        }
+
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        try (ObjectOutputStream oos = new ObjectOutputStream(baos)) {
+            final CharSequenceReader reader = new CharSequenceReader("xFooBarx", 1, 7);
+            oos.writeObject(reader);
+        }
+        try (ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(baos.toByteArray()))) {
+            final CharSequenceReader reader = (CharSequenceReader) ois.readObject();
+            assertEquals('F', reader.read());
+            assertEquals('o', reader.read());
+            assertEquals('o', reader.read());
+            assertEquals('B', reader.read());
+            assertEquals('a', reader.read());
+            assertEquals('r', reader.read());
+            assertEquals(-1, reader.read());
+            assertEquals(-1, reader.read());
+            reader.reset();
+            assertEquals('F', reader.read());
+            assertEquals('o', reader.read());
+            assertEquals('o', reader.read());
+            assertEquals('B', reader.read());
+            assertEquals('a', reader.read());
+            assertEquals('r', reader.read());
+            assertEquals(-1, reader.read());
+            assertEquals(-1, reader.read());
+        }
+    }
 }
diff --git a/src/test/resources/org/apache/commons/io/input/CharSequenceReader.bin b/src/test/resources/org/apache/commons/io/input/CharSequenceReader.bin
new file mode 100644
index 0000000..de6c460
Binary files /dev/null and b/src/test/resources/org/apache/commons/io/input/CharSequenceReader.bin differ