Skip to content

Commit 7b13b4d

Browse files
committed
[IO-782] SequenceReader should close readers when its close method is
called #391.
1 parent 8b13282 commit 7b13b4d

3 files changed

Lines changed: 113 additions & 12 deletions

File tree

src/changes/changes.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,9 @@ The <action> type attribute can be add,update,fix,remove.
210210
<action issue="IO-611" dev="ggregory" type="fix" due-to="ArdenL-Liu, Bruno P. Kinoshita, Gary Gregory">
211211
Better docs in IOUtils and IOUtils.byteArray(int size) #374.
212212
</action>
213+
<action issue="IO-782" dev="ggregory" type="fix" due-to="Matteo Di Giovinazzo, Gary Gregory">
214+
SequenceReader should close readers when its close method is called #391.
215+
</action>
213216
<!-- ADD -->
214217
<action type="add" dev="ggregory" due-to="Gary Gregory">
215218
Add GitHub coverage.yml.

src/main/java/org/apache/commons/io/input/SequenceReader.java

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,19 +20,24 @@
2020

2121
import java.io.IOException;
2222
import java.io.Reader;
23+
import java.io.SequenceInputStream;
24+
import java.io.UncheckedIOException;
2325
import java.util.Arrays;
2426
import java.util.Iterator;
2527
import java.util.Objects;
2628

2729
/**
28-
* Provides the contents of multiple Readers in sequence.
30+
* Provides the contents of multiple {@link Reader}s in sequence.
31+
* <p>
32+
* Like {@link SequenceInputStream} but for {@link Reader} arguments.
33+
* </p>
2934
*
3035
* @since 2.7
3136
*/
3237
public class SequenceReader extends Reader {
3338

3439
private Reader reader;
35-
private Iterator<? extends Reader> readers;
40+
private final Iterator<? extends Reader> readers;
3641

3742
/**
3843
* Constructs a new instance with readers
@@ -41,7 +46,11 @@ public class SequenceReader extends Reader {
4146
*/
4247
public SequenceReader(final Iterable<? extends Reader> readers) {
4348
this.readers = Objects.requireNonNull(readers, "readers").iterator();
44-
this.reader = nextReader();
49+
try {
50+
this.reader = nextReader();
51+
} catch (final IOException e) {
52+
throw new UncheckedIOException(e);
53+
}
4554
}
4655

4756
/**
@@ -60,17 +69,27 @@ public SequenceReader(final Reader... readers) {
6069
*/
6170
@Override
6271
public void close() throws IOException {
63-
this.readers = null;
64-
this.reader = null;
72+
do { // NOPMD
73+
// empty
74+
} while (nextReader() != null);
6575
}
6676

6777
/**
6878
* Returns the next available reader or null if done.
6979
*
70-
* @return the next available reader or null
80+
* @return the next available reader or null.
81+
* @throws IOException IOException If an I/O error occurs.
7182
*/
72-
private Reader nextReader() {
73-
return this.readers.hasNext() ? this.readers.next() : null;
83+
private Reader nextReader() throws IOException {
84+
if (reader != null) {
85+
reader.close();
86+
}
87+
if (readers.hasNext()) {
88+
reader = readers.next();
89+
} else {
90+
reader = null;
91+
}
92+
return reader;
7493
}
7594

7695
/*
@@ -86,7 +105,7 @@ public int read() throws IOException {
86105
if (c != EOF) {
87106
break;
88107
}
89-
reader = nextReader();
108+
nextReader();
90109
}
91110
return c;
92111
}
@@ -106,7 +125,7 @@ public int read(final char[] cbuf, int off, int len) throws IOException {
106125
while (reader != null) {
107126
final int readLen = reader.read(cbuf, off, len);
108127
if (readLen == EOF) {
109-
reader = nextReader();
128+
nextReader();
110129
} else {
111130
count += readLen;
112131
off += readLen;

src/test/java/org/apache/commons/io/input/SequenceReaderTest.java

Lines changed: 81 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,18 @@
1616
*/
1717
package org.apache.commons.io.input;
1818

19+
import static org.apache.commons.io.IOUtils.EOF;
1920
import static org.junit.jupiter.api.Assertions.assertEquals;
2021
import static org.junit.jupiter.api.Assertions.assertFalse;
2122
import static org.junit.jupiter.api.Assertions.assertThrows;
23+
import static org.junit.jupiter.api.Assertions.assertTrue;
2224

2325
import java.io.IOException;
2426
import java.io.Reader;
2527
import java.io.StringReader;
2628
import java.util.ArrayList;
2729
import java.util.Collection;
2830
import java.util.List;
29-
3031
import org.apache.commons.lang3.StringUtils;
3132
import org.junit.jupiter.api.Test;
3233

@@ -35,6 +36,33 @@
3536
*/
3637
public class SequenceReaderTest {
3738

39+
private static class CustomReader extends Reader {
40+
41+
boolean closed;
42+
43+
protected void checkOpen() throws IOException {
44+
if (closed) {
45+
throw new IOException("emptyReader already closed");
46+
}
47+
}
48+
49+
@Override
50+
public int read(final char[] cbuf, final int off, final int len) throws IOException {
51+
checkOpen();
52+
close();
53+
return EOF;
54+
}
55+
56+
@Override
57+
public void close() throws IOException {
58+
closed = true;
59+
}
60+
61+
public boolean isClosed() {
62+
return closed;
63+
}
64+
};
65+
3866
private static final char NUL = 0;
3967

4068
private void checkArray(final char[] expected, final char[] actual) {
@@ -56,14 +84,65 @@ private void checkReadEof(final Reader reader) throws IOException {
5684
}
5785

5886
@Test
59-
public void testClose() throws IOException {
87+
public void testAutoClose() throws IOException {
6088
try (Reader reader = new SequenceReader(new CharSequenceReader("FooBar"))) {
6189
checkRead(reader, "Foo");
6290
reader.close();
6391
checkReadEof(reader);
6492
}
6593
}
6694

95+
@Test
96+
public void testClose() throws IOException {
97+
final Reader reader = new SequenceReader(new CharSequenceReader("FooBar"));
98+
checkRead(reader, "Foo");
99+
reader.close();
100+
checkReadEof(reader);
101+
}
102+
103+
@Test
104+
public void testCloseReaders() throws IOException {
105+
final CustomReader reader0 = new CustomReader();
106+
final CustomReader reader1 = new CustomReader() {
107+
108+
private final char[] content = {'A'};
109+
private int position;
110+
111+
@Override
112+
public int read(final char[] cbuf, final int off, final int len) throws IOException {
113+
checkOpen();
114+
115+
if (off < 0) {
116+
throw new IndexOutOfBoundsException("off is negative");
117+
} else if (len < 0) {
118+
throw new IndexOutOfBoundsException("len is negative");
119+
} else if (len > cbuf.length - off) {
120+
throw new IndexOutOfBoundsException("len is greater than cbuf.length - off");
121+
}
122+
123+
if (position > 0) {
124+
return EOF;
125+
}
126+
127+
cbuf[off] = content[0];
128+
position++;
129+
return 1;
130+
}
131+
132+
};
133+
134+
try (SequenceReader sequenceReader = new SequenceReader(reader1, reader0)) {
135+
assertEquals('A', sequenceReader.read());
136+
assertEquals(EOF, sequenceReader.read());
137+
} finally {
138+
assertTrue(reader1.isClosed());
139+
assertTrue(reader0.isClosed());
140+
}
141+
assertTrue(reader1.isClosed());
142+
assertTrue(reader0.isClosed());
143+
144+
}
145+
67146
@Test
68147
public void testMarkSupported() throws Exception {
69148
try (Reader reader = new SequenceReader()) {

0 commit comments

Comments
 (0)