Skip to content

Commit 972a5a4

Browse files
committed
[IO-458] Add a SequenceReader similar to java.io.SequenceInputStream.
This new class is based on Joshua Gitlin's PR #99 but redone to cover a much broader range of types through an Iterable ctor instead of a List. Closes #99.
1 parent e46146c commit 972a5a4

5 files changed

Lines changed: 315 additions & 2 deletions

File tree

checkstyle.xml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ limitations under the License.
3131
<property name="fileExtensions" value="java,xml"/>
3232
</module>
3333
<module name="TreeWalker">
34-
<property name="cacheFile" value="target/cachefile"/>
3534
<module name="AvoidStarImport"/>
3635
<module name="RedundantImport"/>
3736
<module name="UnusedImports"/>

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -286,7 +286,7 @@ file comparators, endian transformation classes, and much more.
286286
</commons.osgi.export>
287287
<commons.scmPubUrl>https://svn.apache.org/repos/infra/websites/production/commons/content/proper/commons-io/</commons.scmPubUrl>
288288
<commons.scmPubCheckoutDirectory>site-content</commons.scmPubCheckoutDirectory>
289-
<checkstyle.plugin.version>3.0.0</checkstyle.plugin.version>
289+
<checkstyle.plugin.version>3.1.0</checkstyle.plugin.version>
290290
<commons.jacoco.version>0.8.4</commons.jacoco.version>
291291
<commons.surefire.version>2.22.2</commons.surefire.version>
292292
<commons.japicmp.version>0.14.1</commons.japicmp.version>

src/changes/changes.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,9 @@ The <action> type attribute can be add,update,fix,remove.
180180
<action issue="IO-645" dev="ggregory" type="add" due-to="Gary Gregory">
181181
Add org.apache.commons.io.file.PathUtils.fileContentEquals(Path, Path, OpenOption...).
182182
</action>
183+
<action issue="IO-458" dev="ggregory" type="add" due-to="Gary Gregory, Joshua Gitlin">
184+
Add a SequenceReader similar to java.io.SequenceInputStream.
185+
</action>
183186
</release>
184187

185188
<release version="2.6" date="2017-10-15" description="Java 7 required, Java 9 supported.">
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package org.apache.commons.io.input;
18+
19+
import static org.apache.commons.io.IOUtils.EOF;
20+
21+
import java.io.IOException;
22+
import java.io.Reader;
23+
import java.util.Arrays;
24+
import java.util.Iterator;
25+
import java.util.Objects;
26+
27+
/**
28+
* Provides the contents of multiple Readers in sequence.
29+
*
30+
* @since 2.7
31+
*/
32+
public class SequenceReader extends Reader {
33+
34+
private Reader reader;
35+
private Iterator<? extends Reader> readers;
36+
37+
/**
38+
* Construct a new instance with readers
39+
*
40+
* @param readers the readers to read
41+
*/
42+
public SequenceReader(final Iterable<? extends Reader> readers) {
43+
this.readers = Objects.requireNonNull(readers, "readers").iterator();
44+
this.reader = this.readers.hasNext() ? this.readers.next() : null;
45+
}
46+
47+
/**
48+
* Construct a new instance with readers
49+
*
50+
* @param readers the readers to read
51+
*/
52+
public SequenceReader(final Reader... readers) {
53+
this(Arrays.asList(readers));
54+
}
55+
56+
/*
57+
* (non-Javadoc)
58+
*
59+
* @see java.io.Reader#close()
60+
*/
61+
@Override
62+
public void close() throws IOException {
63+
this.readers = null;
64+
this.reader = null;
65+
}
66+
67+
/*
68+
* (non-Javadoc)
69+
*
70+
* @see java.io.Reader#read(char[], int, int)
71+
*/
72+
@Override
73+
public int read() throws IOException {
74+
int c = EOF;
75+
while (reader != null) {
76+
c = reader.read();
77+
if (c == EOF) {
78+
reader = readers.hasNext() ? readers.next() : null;
79+
} else {
80+
break;
81+
}
82+
}
83+
return c;
84+
}
85+
86+
/*
87+
* (non-Javadoc)
88+
*
89+
* @see java.io.Reader#read()
90+
*/
91+
@Override
92+
public int read(final char[] cbuf, int off, int len) throws IOException {
93+
Objects.requireNonNull(cbuf, "cbuf");
94+
if (len < 0 || off < 0 || off + len > cbuf.length) {
95+
throw new IndexOutOfBoundsException("Array Size=" + cbuf.length + ", offset=" + off + ", length=" + len);
96+
}
97+
int count = 0;
98+
while (reader != null) {
99+
final int readLen = reader.read(cbuf, off, len);
100+
if (readLen == EOF) {
101+
reader = readers.hasNext() ? readers.next() : null;
102+
} else {
103+
count += readLen;
104+
off += readLen;
105+
len -= readLen;
106+
if (len <= 0) {
107+
break;
108+
}
109+
}
110+
}
111+
if (count > 0) {
112+
return count;
113+
}
114+
return EOF;
115+
}
116+
}
Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package org.apache.commons.io.input;
18+
19+
import static org.junit.jupiter.api.Assertions.assertEquals;
20+
import static org.junit.jupiter.api.Assertions.assertThrows;
21+
import static org.junit.jupiter.api.Assertions.assertFalse;
22+
import static org.junit.jupiter.api.Assertions.assertTrue;
23+
24+
import java.io.IOException;
25+
import java.io.Reader;
26+
import java.io.StringReader;
27+
import java.util.ArrayList;
28+
import java.util.Collection;
29+
import java.util.List;
30+
31+
import org.apache.commons.lang3.StringUtils;
32+
import org.junit.jupiter.api.Test;
33+
34+
/**
35+
* Test case for {@link SequenceReader}.
36+
*/
37+
public class SequenceReaderTest {
38+
39+
private static final char NUL = 0;
40+
41+
private void checkArray(final char[] expected, final char[] actual) {
42+
for (int i = 0; i < expected.length; i++) {
43+
assertEquals(expected[i], actual[i], "Compare[" + i + "]");
44+
}
45+
}
46+
47+
private void checkRead(final Reader reader, final String expected) throws IOException {
48+
for (int i = 0; i < expected.length(); i++) {
49+
assertEquals(expected.charAt(i), (char) reader.read(), "Read[" + i + "] of '" + expected + "'");
50+
}
51+
}
52+
53+
private void checkReadEof(final Reader reader) throws IOException {
54+
for (int i = 0; i < 10; i++) {
55+
assertEquals(-1, reader.read());
56+
}
57+
}
58+
59+
@Test
60+
public void testClose() throws IOException {
61+
try (final Reader reader = new SequenceReader(new CharSequenceReader("FooBar"))) {
62+
checkRead(reader, "Foo");
63+
reader.close();
64+
checkReadEof(reader);
65+
}
66+
}
67+
68+
@Test
69+
public void testReadClosedReader() throws IOException {
70+
@SuppressWarnings("resource")
71+
final Reader reader = new SequenceReader(new CharSequenceReader("FooBar"));
72+
reader.close();
73+
checkReadEof(reader);
74+
}
75+
76+
@Test
77+
public void testMarkSupported() throws Exception {
78+
try (final Reader reader = new SequenceReader()) {
79+
assertFalse(reader.markSupported());
80+
}
81+
}
82+
83+
@Test
84+
public void testRead() throws IOException {
85+
try (final Reader reader = new SequenceReader(new StringReader("Foo"), new StringReader("Bar"))) {
86+
assertEquals('F', reader.read());
87+
assertEquals('o', reader.read());
88+
assertEquals('o', reader.read());
89+
assertEquals('B', reader.read());
90+
assertEquals('a', reader.read());
91+
assertEquals('r', reader.read());
92+
checkReadEof(reader);
93+
}
94+
}
95+
96+
@Test
97+
public void testReadCharArray() throws IOException {
98+
try (final Reader reader = new SequenceReader(new StringReader("Foo"), new StringReader("Bar"))) {
99+
char[] chars = new char[2];
100+
assertEquals(2, reader.read(chars));
101+
checkArray(new char[] { 'F', 'o' }, chars);
102+
chars = new char[3];
103+
assertEquals(3, reader.read(chars));
104+
checkArray(new char[] { 'o', 'B', 'a' }, chars);
105+
chars = new char[3];
106+
assertEquals(1, reader.read(chars));
107+
checkArray(new char[] { 'r', NUL, NUL }, chars);
108+
assertEquals(-1, reader.read(chars));
109+
}
110+
}
111+
112+
@Test
113+
public void testReadCharArrayPortion() throws IOException {
114+
final char[] chars = new char[10];
115+
try (final Reader reader = new SequenceReader(new StringReader("Foo"), new StringReader("Bar"))) {
116+
assertEquals(3, reader.read(chars, 3, 3));
117+
checkArray(new char[] { NUL, NUL, NUL, 'F', 'o', 'o' }, chars);
118+
assertEquals(3, reader.read(chars, 0, 3));
119+
checkArray(new char[] { 'B', 'a', 'r', 'F', 'o', 'o', NUL }, chars);
120+
assertEquals(-1, reader.read(chars));
121+
assertThrows(IndexOutOfBoundsException.class, () -> reader.read(chars, 10, 10));
122+
assertThrows(NullPointerException.class, () -> reader.read(null, 0, 10));
123+
}
124+
}
125+
126+
@Test
127+
public void testReadCollection() throws IOException {
128+
Collection<Reader> readers = new ArrayList<>();
129+
readers.add(new StringReader("F"));
130+
readers.add(new StringReader("B"));
131+
try (final Reader reader = new SequenceReader(readers)) {
132+
assertEquals('F', reader.read());
133+
assertEquals('B', reader.read());
134+
checkReadEof(reader);
135+
}
136+
}
137+
138+
@Test
139+
public void testReadIterable() throws IOException {
140+
Collection<Reader> readers = new ArrayList<>();
141+
readers.add(new StringReader("F"));
142+
readers.add(new StringReader("B"));
143+
Iterable<Reader> iterable = readers;
144+
try (final Reader reader = new SequenceReader(iterable)) {
145+
assertEquals('F', reader.read());
146+
assertEquals('B', reader.read());
147+
checkReadEof(reader);
148+
}
149+
}
150+
151+
@Test
152+
public void testReadLength0Readers() throws IOException {
153+
try (final Reader reader = new SequenceReader(new StringReader(StringUtils.EMPTY),
154+
new StringReader(StringUtils.EMPTY), new StringReader(StringUtils.EMPTY))) {
155+
checkReadEof(reader);
156+
}
157+
}
158+
159+
@Test
160+
public void testReadLength1Readers() throws IOException {
161+
try (final Reader reader = new SequenceReader(
162+
// @formatter:off
163+
new StringReader("0"),
164+
new StringReader("1"),
165+
new StringReader("2"),
166+
new StringReader("3"))) {
167+
// @formatter:on
168+
assertEquals('0', reader.read());
169+
assertEquals('1', reader.read());
170+
assertEquals('2', reader.read());
171+
assertEquals('3', reader.read());
172+
}
173+
}
174+
175+
@Test
176+
public void testReadList() throws IOException {
177+
List<Reader> readers = new ArrayList<>();
178+
readers.add(new StringReader("F"));
179+
readers.add(new StringReader("B"));
180+
try (final Reader reader = new SequenceReader(readers)) {
181+
assertEquals('F', reader.read());
182+
assertEquals('B', reader.read());
183+
checkReadEof(reader);
184+
}
185+
}
186+
187+
@Test
188+
public void testSkip() throws IOException {
189+
try (final Reader reader = new SequenceReader(new StringReader("Foo"), new StringReader("Bar"))) {
190+
assertEquals(3, reader.skip(3));
191+
checkRead(reader, "Bar");
192+
assertEquals(0, reader.skip(3));
193+
}
194+
}
195+
}

0 commit comments

Comments
 (0)