Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public class SeekableInMemoryByteChannel implements SeekableByteChannel {
private static final int NAIVE_RESIZE_LIMIT = Integer.MAX_VALUE >> 1;
private byte[] data;
private final AtomicBoolean closed = new AtomicBoolean();
private int position;
private long position;
private int size;

/**
Expand Down Expand Up @@ -113,25 +113,28 @@ public long position() throws ClosedChannelException {
@Override
public SeekableByteChannel position(final long newPosition) throws IOException {
ensureOpen();
if (newPosition < 0L || newPosition > Integer.MAX_VALUE) {
throw new IllegalArgumentException(String.format("Position must be in range [0..%,d]: %,d", Integer.MAX_VALUE, newPosition));
if (newPosition < 0L) {
throw new IllegalArgumentException(String.format("New position is negative: %,d", newPosition));
}
position = (int) newPosition;
position = newPosition;
return this;
}

@Override
public int read(final ByteBuffer buf) throws IOException {
ensureOpen();
if (position > Integer.MAX_VALUE) {
return -1;
}
int wanted = buf.remaining();
final int possible = size - position;
final int possible = size - (int) position;
if (possible <= 0) {
return -1;
}
if (wanted > possible) {
wanted = possible;
}
buf.put(data, position, wanted);
buf.put(data, (int) position, wanted);
position += wanted;
return wanted;
}
Expand Down Expand Up @@ -160,36 +163,41 @@ public long size() throws ClosedChannelException {
@Override
public SeekableByteChannel truncate(final long newSize) throws ClosedChannelException {
ensureOpen();
if (newSize < 0L || newSize > Integer.MAX_VALUE) {
throw new IllegalArgumentException("Size must be range [0.." + Integer.MAX_VALUE + "]");
if (newSize < 0L) {
throw new IllegalArgumentException(String.format("New size is negative: %,d", newSize));
}
if (size > newSize) {
size = (int) newSize;
}
if (position > newSize) {
position = (int) newSize;
position = newSize;
}
return this;
}

@Override
public int write(final ByteBuffer b) throws IOException {
ensureOpen();
if (position > Integer.MAX_VALUE) {
throw new IOException("position > Integer.MAX_VALUE");
}
int wanted = b.remaining();
final int possibleWithoutResize = size - position;
final int intPos = (int) position;
final int possibleWithoutResize = size - intPos;
if (wanted > possibleWithoutResize) {
final int newSize = position + wanted;
final int newSize = intPos + wanted;
if (newSize < 0) { // overflow
resize(Integer.MAX_VALUE);
wanted = Integer.MAX_VALUE - position;
wanted = Integer.MAX_VALUE - intPos;
} else {
Comment on lines 190 to 193
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't catch it before, because I didn't have the Javadoc in mind, however the generic WritableByteChannel#write Javadoc says:

Unless otherwise specified, a write operation will return only after writing all of the r requested bytes.

So this edge case should throw instead of decreasing wanted. Do you agree?

resize(newSize);
}
}
b.get(data, position, wanted);
b.get(data, intPos, wanted);
position += wanted;
if (size < position) {
size = position;
final int intPos2 = (int) position;
if (size < intPos2) {
size = intPos2;
Copy link
Contributor

@ppkarwasz ppkarwasz Dec 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: What about reusing intPos? Also a comment might be useful to explain that intPos + wanted is at most (Integer.MAX_VALUE - intPos) + intPos.

        position = intPos += wanted;
        if (size < intPos) {
            size = intPos;

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

}
return wanted;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,16 +152,33 @@ void testShouldThrowExceptionOnWritingToClosedChannel() {
}

@Test
void testShouldThrowExceptionWhenSettingIncorrectPosition() {
void testShouldThrowWhenSettingIncorrectPosition() throws IOException {
try (SeekableInMemoryByteChannel c = new SeekableInMemoryByteChannel()) {
assertThrows(IllegalArgumentException.class, () -> c.position(Integer.MAX_VALUE + 1L));
final ByteBuffer buffer = ByteBuffer.allocate(1);
c.position(c.size() + 1);
assertEquals(c.size() + 1, c.position());
assertEquals(-1, c.read(buffer));
c.position(Integer.MAX_VALUE + 1L);
assertEquals(Integer.MAX_VALUE + 1L, c.position());
assertEquals(-1, c.read(buffer));
assertThrows(IOException.class, () -> c.write(buffer));
assertThrows(IllegalArgumentException.class, () -> c.position(-1));
assertThrows(IllegalArgumentException.class, () -> c.position(Integer.MIN_VALUE));
assertThrows(IllegalArgumentException.class, () -> c.position(Long.MIN_VALUE));
}
}

@Test
void testShouldThrowExceptionWhenTruncatingToIncorrectSize() {
void testShouldThrowWhenTruncatingToIncorrectSize() throws IOException {
try (SeekableInMemoryByteChannel c = new SeekableInMemoryByteChannel()) {
assertThrows(IllegalArgumentException.class, () -> c.truncate(Integer.MAX_VALUE + 1L));
final ByteBuffer buffer = ByteBuffer.allocate(1);
c.truncate(c.size() + 1);
assertEquals(1, c.read(buffer));
c.truncate(Integer.MAX_VALUE + 1L);
assertEquals(0, c.read(buffer));
assertThrows(IllegalArgumentException.class, () -> c.truncate(-1));
assertThrows(IllegalArgumentException.class, () -> c.truncate(Integer.MIN_VALUE));
assertThrows(IllegalArgumentException.class, () -> c.truncate(Long.MIN_VALUE));
}
}

Expand Down