Skip to content

Commit 80c82bf

Browse files
committed
Better tests
Serialize reads and writes in ByteArraySeekableByteChannel
1 parent cfd3ab7 commit 80c82bf

File tree

7 files changed

+940
-421
lines changed

7 files changed

+940
-421
lines changed

src/main/java/org/apache/commons/io/IOUtils.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -226,8 +226,10 @@ public class IOUtils {
226226
* <p>
227227
* The constant is copied from OpenJDK's {@link jdk.internal.util.ArraysSupport#SOFT_MAX_ARRAY_LENGTH}.
228228
* </p>
229+
*
230+
* @since 2.21.0
229231
*/
230-
private static final int SOFT_MAX_ARRAY_LENGTH = Integer.MAX_VALUE - 8;
232+
public static final int SOFT_MAX_ARRAY_LENGTH = Integer.MAX_VALUE - 8;
231233

232234
/**
233235
* Returns the given InputStream if it is already a {@link BufferedInputStream}, otherwise creates a

src/main/java/org/apache/commons/io/channels/ByteArraySeekableByteChannel.java

Lines changed: 91 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -25,38 +25,34 @@
2525
import java.nio.channels.SeekableByteChannel;
2626
import java.util.Arrays;
2727
import java.util.concurrent.atomic.AtomicBoolean;
28+
import java.util.concurrent.locks.ReentrantLock;
2829

2930
import org.apache.commons.io.IOUtils;
3031

3132
/**
3233
* A {@link SeekableByteChannel} implementation backed by a byte array.
3334
* <p>
34-
* When this channel is used for writing, an internal buffer grows to accommodate incoming data. The natural size limit is the value of
35-
* {@link Integer#MAX_VALUE} and it's not possible to {@link #position(long) set the position} or {@link #truncate(long) truncate} to a value bigger than that.
36-
* The raw internal buffer is accessed via {@link ByteArraySeekableByteChannel#array()}.
37-
* </p>
38-
* <p>
39-
* This class never throws {@link ClosedChannelException} because a byte array is not a resource you open or close.
40-
* </p>
41-
* <p>
42-
* This class isn't thread-safe.
35+
* When used for writing, the internal buffer grows to accommodate incoming data. The natural size limit is the value of {@link IOUtils#SOFT_MAX_ARRAY_LENGTH}
36+
* and it's not possible to {@link #position(long) set the position} or {@link #truncate(long) truncate} to a value bigger than that. The raw internal buffer is
37+
* accessed via {@link ByteArraySeekableByteChannel#array()}.
4338
* </p>
4439
*
4540
* @since 2.19.0
4641
*/
4742
public class ByteArraySeekableByteChannel implements SeekableByteChannel {
4843

49-
private static final int NAIVE_RESIZE_LIMIT = Integer.MAX_VALUE >> 1;
44+
private static final int RESIZE_LIMIT = Integer.MAX_VALUE >> 1;
5045
private byte[] data;
5146
private final AtomicBoolean closed = new AtomicBoolean();
5247
private int position;
5348
private int size;
49+
private final ReentrantLock lock = new ReentrantLock();
5450

5551
/**
5652
* Constructs a new instance using a default empty buffer.
5753
*/
5854
public ByteArraySeekableByteChannel() {
59-
this(IOUtils.DEFAULT_BUFFER_SIZE);
55+
this(0);
6056
}
6157

6258
/**
@@ -90,17 +86,24 @@ public byte[] array() {
9086
return data;
9187
}
9288

93-
@Override
94-
public void close() {
95-
closed.set(true);
96-
}
97-
98-
private void ensureOpen() throws ClosedChannelException {
89+
private void checkOpen() throws ClosedChannelException {
9990
if (!isOpen()) {
10091
throw new ClosedChannelException();
10192
}
10293
}
10394

95+
private int checkRange(final long newSize, final String method) {
96+
if (newSize < 0L || newSize > IOUtils.SOFT_MAX_ARRAY_LENGTH) {
97+
throw new IllegalArgumentException(String.format("%s must be in range [0..%,d]: %,d", method, IOUtils.SOFT_MAX_ARRAY_LENGTH, newSize));
98+
}
99+
return (int) newSize;
100+
}
101+
102+
@Override
103+
public void close() {
104+
closed.set(true);
105+
}
106+
104107
/**
105108
* Like {@link #size()} but never throws {@link ClosedChannelException}.
106109
*
@@ -117,42 +120,55 @@ public boolean isOpen() {
117120

118121
@Override
119122
public long position() throws ClosedChannelException {
120-
ensureOpen();
121-
return position;
123+
checkOpen();
124+
lock.lock();
125+
try {
126+
return position;
127+
} finally {
128+
lock.unlock();
129+
}
122130
}
123131

124132
@Override
125133
public SeekableByteChannel position(final long newPosition) throws IOException {
126-
ensureOpen();
127-
if (newPosition < 0L || newPosition > Integer.MAX_VALUE) {
128-
throw new IOException("Position must be in range [0.." + Integer.MAX_VALUE + "]");
134+
checkOpen();
135+
final int intPos = checkRange(newPosition, "position()");
136+
lock.lock();
137+
try {
138+
position = intPos;
139+
} finally {
140+
lock.unlock();
129141
}
130-
position = (int) newPosition;
131142
return this;
132143
}
133144

134145
@Override
135146
public int read(final ByteBuffer buf) throws IOException {
136-
ensureOpen();
137-
int wanted = buf.remaining();
138-
final int possible = size - position;
139-
if (possible <= 0) {
140-
return -1;
141-
}
142-
if (wanted > possible) {
143-
wanted = possible;
147+
checkOpen();
148+
lock.lock();
149+
try {
150+
int wanted = buf.remaining();
151+
final int possible = size - position;
152+
if (possible <= 0) {
153+
return IOUtils.EOF;
154+
}
155+
if (wanted > possible) {
156+
wanted = possible;
157+
}
158+
buf.put(data, position, wanted);
159+
position += wanted;
160+
return wanted;
161+
} finally {
162+
lock.unlock();
144163
}
145-
buf.put(data, position, wanted);
146-
position += wanted;
147-
return wanted;
148164
}
149165

150166
private void resize(final int newLength) {
151167
int len = data.length;
152168
if (len <= 0) {
153169
len = 1;
154170
}
155-
if (newLength < NAIVE_RESIZE_LIMIT) {
171+
if (newLength < RESIZE_LIMIT) {
156172
while (len < newLength) {
157173
len <<= 1;
158174
}
@@ -164,44 +180,57 @@ private void resize(final int newLength) {
164180

165181
@Override
166182
public long size() throws ClosedChannelException {
167-
ensureOpen();
168-
return size;
183+
checkOpen();
184+
lock.lock();
185+
try {
186+
return size;
187+
} finally {
188+
lock.unlock();
189+
}
169190
}
170191

171192
@Override
172193
public SeekableByteChannel truncate(final long newSize) throws ClosedChannelException {
173-
ensureOpen();
174-
if (newSize < 0L || newSize > Integer.MAX_VALUE) {
175-
throw new IllegalArgumentException("Size must be range [0.." + Integer.MAX_VALUE + "]");
176-
}
177-
if (size > newSize) {
178-
size = (int) newSize;
179-
}
180-
if (position > newSize) {
181-
position = (int) newSize;
194+
checkOpen();
195+
final int intSize = checkRange(newSize, "truncate()");
196+
lock.lock();
197+
try {
198+
if (size > intSize) {
199+
size = intSize;
200+
}
201+
if (position > intSize) {
202+
position = intSize;
203+
}
204+
} finally {
205+
lock.unlock();
182206
}
183207
return this;
184208
}
185209

186210
@Override
187211
public int write(final ByteBuffer b) throws IOException {
188-
ensureOpen();
189-
int wanted = b.remaining();
190-
final int possibleWithoutResize = size - position;
191-
if (wanted > possibleWithoutResize) {
192-
final int newSize = position + wanted;
193-
if (newSize < 0) { // overflow
194-
resize(Integer.MAX_VALUE);
195-
wanted = Integer.MAX_VALUE - position;
196-
} else {
197-
resize(newSize);
212+
checkOpen();
213+
lock.lock();
214+
try {
215+
int wanted = b.remaining();
216+
final int possibleWithoutResize = size - position;
217+
if (wanted > possibleWithoutResize) {
218+
final int newSize = position + wanted;
219+
if (newSize < 0) { // overflow
220+
resize(IOUtils.SOFT_MAX_ARRAY_LENGTH);
221+
wanted = IOUtils.SOFT_MAX_ARRAY_LENGTH - position;
222+
} else {
223+
resize(newSize);
224+
}
198225
}
226+
b.get(data, position, wanted);
227+
position += wanted;
228+
if (size < position) {
229+
size = position;
230+
}
231+
return wanted;
232+
} finally {
233+
lock.unlock();
199234
}
200-
b.get(data, position, wanted);
201-
position += wanted;
202-
if (size < position) {
203-
size = position;
204-
}
205-
return wanted;
206235
}
207236
}

0 commit comments

Comments
 (0)