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