Introduce ByteBuf#isContiguous() method (#9735)

Motivation

There's currently no way to determine whether an arbitrary ByteBuf
behaves internally like a "singluar" buffer or a composite one, and this
can be important to know when making decisions about how to manipulate
it in an efficient way.

An example of this is the ByteBuf#discardReadBytes() method which
increases the writable bytes for a contiguous buffer (by readerIndex)
but does not for a composite one.

Unfortunately !(buf instanceof CompositeByteBuf) is not reliable, since
for example this will be true in the case of a sliced CompositeByteBuf
or some third-party composite implementation.

isContiguous was chosen over isComposite since we want to assume "not
contiguous" in the unknown/default case - the doc will it clear that
false does not imply composite.

Modifications

- Add ByteBuf#isContiguous() which returns true by default
- Override the "concrete" ByteBuf impls to return true and ensure
wrapped/derived impls delegate it appropriately
- Include some basic unit tests

Result

Better assumptions/decisions possible when manipulating arbitrary
ByteBufs, for example when combining/cumulating them.
This commit is contained in:
Nick Hill 2019-11-06 03:06:25 -08:00 committed by Norman Maurer
parent d13ac5290a
commit 625981a296
17 changed files with 108 additions and 0 deletions

View File

@ -117,4 +117,9 @@ public abstract class AbstractDerivedByteBuf extends AbstractByteBuf {
public ByteBuffer nioBuffer(int index, int length) {
return unwrap().nioBuffer(index, length);
}
@Override
public boolean isContiguous() {
return unwrap().isContiguous();
}
}

View File

@ -123,6 +123,11 @@ abstract class AbstractPooledDerivedByteBuf extends AbstractReferenceCountedByte
return unwrap().hasMemoryAddress();
}
@Override
public boolean isContiguous() {
return unwrap().isContiguous();
}
@Override
public final int nioBufferCount() {
return unwrap().nioBufferCount();

View File

@ -2338,6 +2338,19 @@ public abstract class ByteBuf implements ReferenceCounted, Comparable<ByteBuf> {
*/
public abstract long memoryAddress();
/**
* Returns {@code true} if this {@link ByteBuf} implementation is backed by a single memory region.
* Composite buffer implementations must return false even if they currently hold &le; 1 components.
* For buffers that return {@code true}, it's guaranteed that a successful call to {@link #discardReadBytes()}
* will increase the value of {@link #maxFastWritableBytes()} by the current {@code readerIndex}.
* <p>
* This method will return {@code false} by default, and a {@code false} return value does not necessarily
* mean that the implementation is composite or that it is <i>not</i> backed by a single memory region.
*/
public boolean isContiguous() {
return false;
}
/**
* Decodes this buffer's readable bytes into a string with the specified
* character set name. This method is identical to

View File

@ -939,6 +939,11 @@ public final class EmptyByteBuf extends ByteBuf {
}
}
@Override
public boolean isContiguous() {
return true;
}
@Override
public String toString(Charset charset) {
return "";

View File

@ -213,6 +213,11 @@ abstract class PooledByteBuf<T> extends AbstractReferenceCountedByteBuf {
return new ByteBuffer[] { nioBuffer(index, length) };
}
@Override
public final boolean isContiguous() {
return true;
}
@Override
public final int getBytes(int index, GatheringByteChannel out, int length) throws IOException {
return out.write(duplicateInternalNioBuffer(index, length));

View File

@ -453,6 +453,11 @@ class ReadOnlyByteBufferBuf extends AbstractReferenceCountedByteBuf {
return (ByteBuffer) internalNioBuffer().clear().position(index).limit(index + length);
}
@Override
public final boolean isContiguous() {
return true;
}
@Override
public boolean hasArray() {
return buffer.hasArray();

View File

@ -956,6 +956,11 @@ public class SwappedByteBuf extends ByteBuf {
return buf.hasMemoryAddress();
}
@Override
public boolean isContiguous() {
return buf.isContiguous();
}
@Override
public long memoryAddress() {
return buf.memoryAddress();

View File

@ -595,6 +595,11 @@ public class UnpooledDirectByteBuf extends AbstractReferenceCountedByteBuf {
return new ByteBuffer[] { nioBuffer(index, length) };
}
@Override
public final boolean isContiguous() {
return true;
}
@Override
public ByteBuf copy(int index, int length) {
ensureAccessible();

View File

@ -320,6 +320,11 @@ public class UnpooledHeapByteBuf extends AbstractReferenceCountedByteBuf {
return (ByteBuffer) internalNioBuffer().clear().position(index).limit(index + length);
}
@Override
public final boolean isContiguous() {
return true;
}
@Override
public byte getByte(int index) {
ensureAccessible();

View File

@ -52,6 +52,11 @@ class WrappedByteBuf extends ByteBuf {
return buf.hasMemoryAddress();
}
@Override
public boolean isContiguous() {
return buf.isContiguous();
}
@Override
public final long memoryAddress() {
return buf.memoryAddress();

View File

@ -109,6 +109,13 @@ public abstract class AbstractCompositeByteBufTest extends AbstractByteBufTest {
return false;
}
@Test
public void testIsContiguous() {
ByteBuf buf = newBuffer(4);
assertFalse(buf.isContiguous());
buf.release();
}
/**
* Tests the "getBufferFor" method
*/

View File

@ -114,4 +114,11 @@ public abstract class AbstractPooledByteBufTest extends AbstractByteBufTest {
assertEquals(buffer.writableBytes(), buffer.maxFastWritableBytes());
buffer.release();
}
@Test
public void testIsContiguous() {
ByteBuf buf = newBuffer(4);
assertTrue(buf.isContiguous());
buf.release();
}
}

View File

@ -19,6 +19,8 @@ import static org.junit.Assert.*;
import java.nio.ByteOrder;
import org.junit.Test;
/**
* Tests big-endian direct channel buffers
*/
@ -35,4 +37,11 @@ public class BigEndianDirectByteBufTest extends AbstractByteBufTest {
protected ByteBuf newDirectBuffer(int length, int maxCapacity) {
return new UnpooledDirectByteBuf(UnpooledByteBufAllocator.DEFAULT, length, maxCapacity);
}
@Test
public void testIsContiguous() {
ByteBuf buf = newBuffer(4);
assertTrue(buf.isContiguous());
buf.release();
}
}

View File

@ -33,6 +33,13 @@ public class DuplicatedByteBufTest extends AbstractByteBufTest {
return buffer;
}
@Test
public void testIsContiguous() {
ByteBuf buf = newBuffer(4);
assertEquals(buf.unwrap().isContiguous(), buf.isContiguous());
buf.release();
}
@Test(expected = NullPointerException.class)
public void shouldNotAllowNullInConstructor() {
new DuplicatedByteBuf(null);

View File

@ -22,6 +22,12 @@ import static org.junit.Assert.*;
public class EmptyByteBufTest {
@Test
public void testIsContiguous() {
EmptyByteBuf empty = new EmptyByteBuf(UnpooledByteBufAllocator.DEFAULT);
assertTrue(empty.isContiguous());
}
@Test
public void testIsWritable() {
EmptyByteBuf empty = new EmptyByteBuf(UnpooledByteBufAllocator.DEFAULT);

View File

@ -37,6 +37,13 @@ public class ReadOnlyDirectByteBufferBufTest {
return ByteBuffer.allocateDirect(size);
}
@Test
public void testIsContiguous() {
ByteBuf buf = buffer(allocate(4).asReadOnlyBuffer());
Assert.assertTrue(buf.isContiguous());
buf.release();
}
@Test(expected = IllegalArgumentException.class)
public void testConstructWithWritable() {
buffer(allocate(1));

View File

@ -47,6 +47,13 @@ public class SlicedByteBufTest extends AbstractByteBufTest {
return buffer.slice(offset, length);
}
@Test
public void testIsContiguous() {
ByteBuf buf = newBuffer(4);
assertEquals(buf.unwrap().isContiguous(), buf.isContiguous());
buf.release();
}
@Test(expected = NullPointerException.class)
public void shouldNotAllowNullInConstructor() {
new SlicedByteBuf(null, 0, 0);