Various DataBuffer improvements
This commit introduces various improvements in DataBuffer: - DataBuffer now exposes its read and write position, as well as its capacity and writable byte count. - Added DataBuffer.asByteBuffer(int, int) - DataBufferUtils.read now reads directly into a DataBuffer, rather than copying a ByteBuffer into a DataBuffer - TomcatHttpHandler now reads directly into a DataBuffer Issues: SPR-16068 SPR-16070
This commit is contained in:
@@ -80,8 +80,8 @@ public abstract class AbstractDataBufferAllocatingTestCase {
|
||||
return dataBuffer -> {
|
||||
String value =
|
||||
DataBufferTestUtils.dumpString(dataBuffer, StandardCharsets.UTF_8);
|
||||
assertEquals(expected, value);
|
||||
DataBufferUtils.release(dataBuffer);
|
||||
assertEquals(expected, value);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,6 @@ import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.junit.Test;
|
||||
@@ -33,8 +32,107 @@ import static org.junit.Assert.*;
|
||||
public class DataBufferTests extends AbstractDataBufferAllocatingTestCase {
|
||||
|
||||
@Test
|
||||
public void writeAndRead() {
|
||||
public void byteCountsAndPositions() {
|
||||
DataBuffer buffer = createDataBuffer(2);
|
||||
|
||||
assertEquals(0, buffer.readPosition());
|
||||
assertEquals(0, buffer.writePosition());
|
||||
assertEquals(0, buffer.readableByteCount());
|
||||
assertEquals(2, buffer.writableByteCount());
|
||||
assertEquals(2, buffer.capacity());
|
||||
|
||||
buffer.write((byte) 'a');
|
||||
assertEquals(0, buffer.readPosition());
|
||||
assertEquals(1, buffer.writePosition());
|
||||
assertEquals(1, buffer.readableByteCount());
|
||||
assertEquals(1, buffer.writableByteCount());
|
||||
assertEquals(2, buffer.capacity());
|
||||
|
||||
buffer.write((byte) 'b');
|
||||
assertEquals(0, buffer.readPosition());
|
||||
assertEquals(2, buffer.writePosition());
|
||||
assertEquals(2, buffer.readableByteCount());
|
||||
assertEquals(0, buffer.writableByteCount());
|
||||
assertEquals(2, buffer.capacity());
|
||||
|
||||
buffer.read();
|
||||
assertEquals(1, buffer.readPosition());
|
||||
assertEquals(2, buffer.writePosition());
|
||||
assertEquals(1, buffer.readableByteCount());
|
||||
assertEquals(0, buffer.writableByteCount());
|
||||
assertEquals(2, buffer.capacity());
|
||||
|
||||
buffer.read();
|
||||
assertEquals(2, buffer.readPosition());
|
||||
assertEquals(2, buffer.writePosition());
|
||||
assertEquals(0, buffer.readableByteCount());
|
||||
assertEquals(0, buffer.writableByteCount());
|
||||
assertEquals(2, buffer.capacity());
|
||||
|
||||
release(buffer);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void readPositionSmallerThanZero() {
|
||||
DataBuffer buffer = createDataBuffer(1);
|
||||
try {
|
||||
buffer.readPosition(-1);
|
||||
fail("IndexOutOfBoundsException expected");
|
||||
}
|
||||
catch (IndexOutOfBoundsException ignored) {
|
||||
}
|
||||
finally {
|
||||
release(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void readPositionGreaterThanWritePosition() {
|
||||
DataBuffer buffer = createDataBuffer(1);
|
||||
try {
|
||||
buffer.readPosition(1);
|
||||
fail("IndexOutOfBoundsException expected");
|
||||
}
|
||||
catch (IndexOutOfBoundsException ignored) {
|
||||
}
|
||||
finally {
|
||||
release(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void writePositionSmallerThanReadPosition() {
|
||||
DataBuffer buffer = createDataBuffer(2);
|
||||
try {
|
||||
buffer.write((byte) 'a');
|
||||
buffer.read();
|
||||
|
||||
buffer.writePosition(0);
|
||||
fail("IndexOutOfBoundsException expected");
|
||||
}
|
||||
catch (IndexOutOfBoundsException ignored) {
|
||||
}
|
||||
finally {
|
||||
release(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void writePositionGreaterThanCapacity() {
|
||||
DataBuffer buffer = createDataBuffer(1);
|
||||
try {
|
||||
buffer.writePosition(2);
|
||||
fail("IndexOutOfBoundsException expected");
|
||||
}
|
||||
catch (IndexOutOfBoundsException ignored) {
|
||||
}
|
||||
finally {
|
||||
release(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void writeAndRead() {
|
||||
DataBuffer buffer = createDataBuffer(5);
|
||||
buffer.write(new byte[]{'a', 'b', 'c'});
|
||||
|
||||
@@ -54,33 +152,32 @@ public class DataBufferTests extends AbstractDataBufferAllocatingTestCase {
|
||||
|
||||
@Test
|
||||
public void inputStream() throws IOException {
|
||||
byte[] data = new byte[]{'a', 'b', 'c', 'd', 'e'};
|
||||
|
||||
DataBuffer buffer = createDataBuffer(4);
|
||||
buffer.write(data);
|
||||
|
||||
buffer.read(); // readIndex++
|
||||
buffer.write(new byte[]{'a', 'b', 'c', 'd', 'e'});
|
||||
buffer.readPosition(1);
|
||||
|
||||
InputStream inputStream = buffer.asInputStream();
|
||||
|
||||
int available = inputStream.available();
|
||||
assertEquals(4, available);
|
||||
assertEquals(4, inputStream.available());
|
||||
|
||||
int result = inputStream.read();
|
||||
assertEquals('b', result);
|
||||
|
||||
available = inputStream.available();
|
||||
assertEquals(3, available);
|
||||
assertEquals(3, inputStream.available());
|
||||
|
||||
byte[] bytes = new byte[2];
|
||||
int len = inputStream.read(bytes);
|
||||
assertEquals(2, len);
|
||||
assertArrayEquals(new byte[]{'c', 'd'}, bytes);
|
||||
assertEquals(1, inputStream.available());
|
||||
|
||||
Arrays.fill(bytes, (byte) 0);
|
||||
len = inputStream.read(bytes);
|
||||
assertEquals(1, len);
|
||||
assertArrayEquals(new byte[]{'e', (byte) 0}, bytes);
|
||||
assertEquals(0, inputStream.available());
|
||||
|
||||
assertEquals(-1, inputStream.read());
|
||||
assertEquals(-1, inputStream.read(bytes));
|
||||
|
||||
release(buffer);
|
||||
}
|
||||
@@ -91,7 +188,8 @@ public class DataBufferTests extends AbstractDataBufferAllocatingTestCase {
|
||||
buffer.write((byte) 'a');
|
||||
|
||||
OutputStream outputStream = buffer.asOutputStream();
|
||||
outputStream.write(new byte[]{'b', 'c', 'd'});
|
||||
outputStream.write('b');
|
||||
outputStream.write(new byte[]{'c', 'd'});
|
||||
|
||||
buffer.write((byte) 'e');
|
||||
|
||||
@@ -106,21 +204,60 @@ public class DataBufferTests extends AbstractDataBufferAllocatingTestCase {
|
||||
public void expand() {
|
||||
DataBuffer buffer = createDataBuffer(1);
|
||||
buffer.write((byte) 'a');
|
||||
assertEquals(1, buffer.capacity());
|
||||
buffer.write((byte) 'b');
|
||||
|
||||
byte[] result = new byte[2];
|
||||
buffer.read(result);
|
||||
assertArrayEquals(new byte[]{'a', 'b'}, result);
|
||||
|
||||
buffer.write(new byte[]{'c', 'd'});
|
||||
|
||||
result = new byte[2];
|
||||
buffer.read(result);
|
||||
assertArrayEquals(new byte[]{'c', 'd'}, result);
|
||||
assertTrue(buffer.capacity() > 1);
|
||||
|
||||
release(buffer);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void increaseCapacity() {
|
||||
DataBuffer buffer = createDataBuffer(1);
|
||||
assertEquals(1, buffer.capacity());
|
||||
|
||||
buffer.capacity(2);
|
||||
assertEquals(2, buffer.capacity());
|
||||
|
||||
release(buffer);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void decreaseCapacityLowReadPosition() {
|
||||
DataBuffer buffer = createDataBuffer(2);
|
||||
buffer.writePosition(2);
|
||||
buffer.capacity(1);
|
||||
assertEquals(1, buffer.capacity());
|
||||
|
||||
release(buffer);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void decreaseCapacityHighReadPosition() {
|
||||
DataBuffer buffer = createDataBuffer(2);
|
||||
buffer.writePosition(2);
|
||||
buffer.readPosition(2);
|
||||
buffer.capacity(1);
|
||||
assertEquals(1, buffer.capacity());
|
||||
|
||||
release(buffer);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void capacityLessThanZero() {
|
||||
DataBuffer buffer = createDataBuffer(1);
|
||||
try {
|
||||
buffer.capacity(-1);
|
||||
fail("IllegalArgumentException expected");
|
||||
}
|
||||
catch (IllegalArgumentException ignored) {
|
||||
}
|
||||
finally {
|
||||
release(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void writeByteBuffer() {
|
||||
DataBuffer buffer1 = createDataBuffer(1);
|
||||
@@ -176,16 +313,74 @@ public class DataBufferTests extends AbstractDataBufferAllocatingTestCase {
|
||||
buffer.read(); // skip a
|
||||
|
||||
ByteBuffer result = buffer.asByteBuffer();
|
||||
assertEquals(2, result.capacity());
|
||||
|
||||
buffer.write((byte) 'd');
|
||||
assertEquals(2, result.remaining());
|
||||
|
||||
byte[] resultBytes = new byte[2];
|
||||
buffer.read(resultBytes);
|
||||
result.get(resultBytes);
|
||||
assertArrayEquals(new byte[]{'b', 'c'}, resultBytes);
|
||||
|
||||
release(buffer);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void asByteBufferIndexLength() {
|
||||
DataBuffer buffer = createDataBuffer(3);
|
||||
buffer.write(new byte[]{'a', 'b'});
|
||||
|
||||
ByteBuffer result = buffer.asByteBuffer(1, 2);
|
||||
assertEquals(2, result.capacity());
|
||||
|
||||
buffer.write((byte) 'c');
|
||||
assertEquals(2, result.remaining());
|
||||
|
||||
byte[] resultBytes = new byte[2];
|
||||
result.get(resultBytes);
|
||||
assertArrayEquals(new byte[]{'b', 'c'}, resultBytes);
|
||||
|
||||
release(buffer);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void byteBufferContainsDataBufferChanges() {
|
||||
DataBuffer dataBuffer = createDataBuffer(1);
|
||||
ByteBuffer byteBuffer = dataBuffer.asByteBuffer(0, 1);
|
||||
|
||||
dataBuffer.write((byte) 'a');
|
||||
|
||||
assertEquals(1, byteBuffer.limit());
|
||||
byte b = byteBuffer.get();
|
||||
assertEquals('a', b);
|
||||
|
||||
release(dataBuffer);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void dataBufferContainsByteBufferChanges() {
|
||||
DataBuffer dataBuffer = createDataBuffer(1);
|
||||
ByteBuffer byteBuffer = dataBuffer.asByteBuffer(0, 1);
|
||||
|
||||
byteBuffer.put((byte) 'a');
|
||||
dataBuffer.writePosition(1);
|
||||
|
||||
byte b = dataBuffer.read();
|
||||
assertEquals('a', b);
|
||||
|
||||
release(dataBuffer);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void emptyAsByteBuffer() {
|
||||
DataBuffer buffer = createDataBuffer(1);
|
||||
|
||||
ByteBuffer result = buffer.asByteBuffer();
|
||||
assertEquals(0, result.capacity());
|
||||
|
||||
release(buffer);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void indexOf() {
|
||||
DataBuffer buffer = createDataBuffer(3);
|
||||
@@ -244,7 +439,7 @@ public class DataBufferTests extends AbstractDataBufferAllocatingTestCase {
|
||||
assertEquals(2, slice.readableByteCount());
|
||||
try {
|
||||
slice.write((byte) 0);
|
||||
fail("IndexOutOfBoundsException expected");
|
||||
fail("Exception expected");
|
||||
}
|
||||
catch (Exception ignored) {
|
||||
}
|
||||
@@ -267,22 +462,4 @@ public class DataBufferTests extends AbstractDataBufferAllocatingTestCase {
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void growDataBuffer() {
|
||||
DataBuffer buffer = stringBuffer("Hello World!");
|
||||
|
||||
byte[] bytes = new byte[5];
|
||||
buffer.read(bytes);
|
||||
assertArrayEquals("Hello".getBytes(StandardCharsets.UTF_8), bytes);
|
||||
|
||||
buffer.write("!!".getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
bytes = new byte[9];
|
||||
buffer.read(bytes);
|
||||
assertArrayEquals(" World!!!".getBytes(StandardCharsets.UTF_8), bytes);
|
||||
|
||||
release(buffer);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -19,8 +19,10 @@ package org.springframework.core.io.buffer;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.URI;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.AsynchronousFileChannel;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.nio.channels.ReadableByteChannel;
|
||||
import java.nio.channels.WritableByteChannel;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
@@ -30,6 +32,7 @@ import java.time.Duration;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.mockito.stubbing.Answer;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
@@ -37,6 +40,8 @@ import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
/**
|
||||
* @author Arjen Poutsma
|
||||
@@ -291,4 +296,56 @@ public class DataBufferUtilsTests extends AbstractDataBufferAllocatingTestCase {
|
||||
// AbstractDataBufferAllocatingTestCase.LeakDetector will assert the release of the buffers
|
||||
}
|
||||
|
||||
@Test
|
||||
public void SPR16070() throws Exception {
|
||||
ReadableByteChannel channel = mock(ReadableByteChannel.class);
|
||||
when(channel.read(any()))
|
||||
.thenAnswer(putByte(1))
|
||||
.thenAnswer(putByte(2))
|
||||
.thenAnswer(putByte(3))
|
||||
.thenReturn(-1);
|
||||
|
||||
Flux<DataBuffer> read = DataBufferUtils.read(channel, this.bufferFactory, 1);
|
||||
|
||||
StepVerifier.create(
|
||||
read.reduce(DataBuffer::write)
|
||||
.map(this::dataBufferToBytes)
|
||||
.map(this::encodeHexString)
|
||||
)
|
||||
.expectNext("010203")
|
||||
.verifyComplete();
|
||||
|
||||
}
|
||||
|
||||
private Answer<Integer> putByte(int b) {
|
||||
return invocation -> {
|
||||
ByteBuffer buffer = invocation.getArgument(0);
|
||||
buffer.put((byte) b);
|
||||
return 1;
|
||||
};
|
||||
}
|
||||
|
||||
private byte[] dataBufferToBytes(DataBuffer buffer) {
|
||||
try {
|
||||
int byteCount = buffer.readableByteCount();
|
||||
byte[] bytes = new byte[byteCount];
|
||||
buffer.read(bytes);
|
||||
return bytes;
|
||||
}
|
||||
finally {
|
||||
release(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
private String encodeHexString(byte[] data) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
for (byte b : data) {
|
||||
builder.append((0xF0 & b) >>> 4);
|
||||
builder.append(0x0F & b);
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user