diff --git a/src/main/java/io/netty/buffer/api/Buf.java b/src/main/java/io/netty/buffer/api/Buf.java index 98404a1..4eb9f15 100644 --- a/src/main/java/io/netty/buffer/api/Buf.java +++ b/src/main/java/io/netty/buffer/api/Buf.java @@ -417,4 +417,11 @@ public interface Buf extends Rc, BufAccessors { * @return A new buffer with independent and exclusive ownership over the read and readable bytes from this buffer. */ Buf bifurcate(); + + /** + * Discards the read bytes, and moves the buffer contents to the beginning of the buffer. + * + * The buffer must be {@linkplain #isOwned() owned}, or an exception will be thrown. + */ + void compact(); } diff --git a/src/main/java/io/netty/buffer/api/CompositeBuf.java b/src/main/java/io/netty/buffer/api/CompositeBuf.java index d3c4f07..676d844 100644 --- a/src/main/java/io/netty/buffer/api/CompositeBuf.java +++ b/src/main/java/io/netty/buffer/api/CompositeBuf.java @@ -20,6 +20,9 @@ import java.nio.ByteOrder; import java.util.Arrays; import java.util.Objects; +import static jdk.incubator.foreign.MemoryAccess.setByteAtOffset; +import static jdk.incubator.foreign.MemoryAccess.setLongAtOffset; + final class CompositeBuf extends RcSupport implements Buf { /** * The max array size is JVM implementation dependant, but most seem to settle on {@code Integer.MAX_VALUE - 8}. @@ -600,6 +603,35 @@ final class CompositeBuf extends RcSupport implements Buf { } } + @Override + public void compact() { + if (!isOwned()) { + throw new IllegalStateException("Buffer must be owned in order to compact."); + } + int distance = roff; + if (distance == 0) { + return; + } + int pos = 0; + var oldOrder = order; + order = ByteOrder.BIG_ENDIAN; + try { + var cursor = openCursor(); + while (cursor.readLong()) { + setLong(pos, cursor.getLong()); + pos += Long.BYTES; + } + while (cursor.readByte()) { + setByte(pos, cursor.getByte()); + pos++; + } + } finally { + order = oldOrder; + } + readerOffset(0); + writerOffset(woff - distance); + } + // @Override public byte readByte() { diff --git a/src/main/java/io/netty/buffer/api/memseg/MemSegBuf.java b/src/main/java/io/netty/buffer/api/memseg/MemSegBuf.java index 6631824..84ea60f 100644 --- a/src/main/java/io/netty/buffer/api/memseg/MemSegBuf.java +++ b/src/main/java/io/netty/buffer/api/memseg/MemSegBuf.java @@ -384,6 +384,29 @@ class MemSegBuf extends RcSupport implements Buf { return bifurcatedBuf; } + @Override + public void compact() { + if (!isOwned()) { + throw new IllegalStateException("Buffer must be owned in order to compact."); + } + int distance = roff; + if (distance == 0) { + return; + } + int pos = 0; + var cursor = openCursor(); + while (cursor.readLong()) { + setLongAtOffset(seg, pos, ByteOrder.BIG_ENDIAN, cursor.getLong()); + pos += Long.BYTES; + } + while (cursor.readByte()) { + setByteAtOffset(seg, pos, cursor.getByte()); + pos++; + } + roff -= distance; + woff -= distance; + } + // @Override public byte readByte() { diff --git a/src/test/java/io/netty/buffer/api/BufTest.java b/src/test/java/io/netty/buffer/api/BufTest.java index bb5da96..a832ed8 100644 --- a/src/test/java/io/netty/buffer/api/BufTest.java +++ b/src/test/java/io/netty/buffer/api/BufTest.java @@ -2195,6 +2195,43 @@ public class BufTest { } } + @ParameterizedTest + @MethodSource("nonSliceAllocators") + public void compactMustDiscardReadBytes(Fixture fixture) { + try (Allocator allocator = fixture.createAllocator(); + Buf buf = allocator.allocate(16, ByteOrder.BIG_ENDIAN)) { + buf.writeLong(0x0102030405060708L).writeInt(0x090A0B0C); + assertEquals(0x01020304, buf.readInt()); + assertEquals(12, buf.writerOffset()); + assertEquals(4, buf.readerOffset()); + assertEquals(4, buf.writableBytes()); + assertEquals(8, buf.readableBytes()); + assertEquals(16, buf.capacity()); + buf.compact(); + assertEquals(8, buf.writerOffset()); + assertEquals(0, buf.readerOffset()); + assertEquals(8, buf.writableBytes()); + assertEquals(8, buf.readableBytes()); + assertEquals(16, buf.capacity()); + assertEquals(0x05060708090A0B0CL, buf.readLong()); + } + } + + @ParameterizedTest + @MethodSource("nonSliceAllocators") + public void compactMustThrowForUnownedBuffer(Fixture fixture) { + try (Allocator allocator = fixture.createAllocator(); + Buf buf = allocator.allocate(8, ByteOrder.BIG_ENDIAN)) { + buf.writeLong(0x0102030405060708L); + assertEquals((byte) 0x01, buf.readByte()); + try (Buf ignore = buf.acquire()) { + assertThrows(IllegalStateException.class, () -> buf.compact()); + assertEquals(1, buf.readerOffset()); + } + assertEquals((byte) 0x02, buf.readByte()); + } + } + // @ParameterizedTest @MethodSource("allocators")