Offer restricted access to DataBuffer's ByteBuffer

This commit introduces DataBuffer::readableByteBuffers and
DataBuffer::writableByteBuffers, allowing restricted access to the
ByteBuffer used internally by DataBuffer implementations.

Closes gh-29943
This commit is contained in:
Arjen Poutsma
2022-11-30 12:30:10 +01:00
parent 72926c29f9
commit 3e2f58cdd2
28 changed files with 773 additions and 275 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,6 +16,7 @@
package org.springframework.core.io.buffer;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
@@ -649,6 +650,71 @@ class DataBufferTests extends AbstractDataBufferAllocatingTests {
release(buffer);
}
@ParameterizedDataBufferAllocatingTest
void toByteBufferDestination(DataBufferFactory bufferFactory) {
super.bufferFactory = bufferFactory;
DataBuffer buffer = createDataBuffer(4);
buffer.write(new byte[]{'a', 'b', 'c'});
ByteBuffer byteBuffer = createByteBuffer(2);
buffer.toByteBuffer(1, byteBuffer, 0, 2);
assertThat(byteBuffer.capacity()).isEqualTo(2);
assertThat(byteBuffer.remaining()).isEqualTo(2);
byte[] resultBytes = new byte[2];
byteBuffer.get(resultBytes);
assertThat(resultBytes).isEqualTo(new byte[]{'b', 'c'});
assertThatExceptionOfType(IndexOutOfBoundsException.class)
.isThrownBy(() -> buffer.toByteBuffer(0, byteBuffer, 0, 3));
release(buffer);
}
@ParameterizedDataBufferAllocatingTest
void readableByteBuffers(DataBufferFactory bufferFactory) throws IOException {
super.bufferFactory = bufferFactory;
DataBuffer dataBuffer = this.bufferFactory.join(Arrays.asList(stringBuffer("a"),
stringBuffer("b"), stringBuffer("c")));
byte[] result = new byte[3];
try (var iterator = dataBuffer.readableByteBuffers()) {
assertThat(iterator).hasNext();
int i = 0;
while (iterator.hasNext()) {
ByteBuffer byteBuffer = iterator.next();
int len = byteBuffer.remaining();
byteBuffer.get(result, i, len);
i += len;
assertThatException().isThrownBy(() -> byteBuffer.put((byte) 'd'));
}
}
assertThat(result).containsExactly('a', 'b', 'c');
release(dataBuffer);
}
@ParameterizedDataBufferAllocatingTest
void writableByteBuffers(DataBufferFactory bufferFactory) {
super.bufferFactory = bufferFactory;
DataBuffer dataBuffer = this.bufferFactory.allocateBuffer(1);
try (DataBuffer.ByteBufferIterator iterator = dataBuffer.writableByteBuffers()) {
assertThat(iterator).hasNext();
ByteBuffer byteBuffer = iterator.next();
byteBuffer.put((byte) 'a');
dataBuffer.writePosition(1);
assertThat(iterator).isExhausted();
}
assertThat(dataBuffer.read()).isEqualTo((byte) 'a');
release(dataBuffer);
}
@ParameterizedDataBufferAllocatingTest
void indexOf(DataBufferFactory bufferFactory) {
@@ -738,6 +804,9 @@ class DataBufferTests extends AbstractDataBufferAllocatingTests {
@ParameterizedDataBufferAllocatingTest
@SuppressWarnings("deprecation")
void retainedSlice(DataBufferFactory bufferFactory) {
assumeFalse(bufferFactory instanceof Netty5DataBufferFactory,
"Netty 5 does not support retainedSlice");
super.bufferFactory = bufferFactory;
DataBuffer buffer = createDataBuffer(3);
@@ -757,12 +826,7 @@ class DataBufferTests extends AbstractDataBufferAllocatingTests {
result = new byte[2];
slice.read(result);
if (!(bufferFactory instanceof Netty5DataBufferFactory)) {
assertThat(result).isEqualTo(new byte[]{'b', 'c'});
}
else {
assertThat(result).isEqualTo(new byte[]{'b', 0});
}
assertThat(result).isEqualTo(new byte[]{'b', 'c'});
release(buffer, slice);
}
@@ -822,7 +886,6 @@ class DataBufferTests extends AbstractDataBufferAllocatingTests {
assertThat(bytes).isEqualTo(new byte[]{'b', 'c'});
DataBuffer buffer2 = createDataBuffer(1);
buffer2.write(new byte[]{'a'});
DataBuffer split2 = buffer2.split(1);
@@ -853,7 +916,7 @@ class DataBufferTests extends AbstractDataBufferAllocatingTests {
byte[] bytes = new byte[3];
composite.read(bytes);
assertThat(bytes).isEqualTo(new byte[] {'a','b','c'});
assertThat(bytes).isEqualTo(new byte[]{'a', 'b', 'c'});
release(composite);
}

View File

@@ -54,7 +54,6 @@ import org.springframework.core.testfixture.io.buffer.AbstractDataBufferAllocati
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.isA;
import static org.mockito.BDDMockito.given;
import static org.mockito.BDDMockito.willAnswer;
import static org.mockito.Mockito.mock;
@@ -115,7 +114,7 @@ class DataBufferUtilsTests extends AbstractDataBufferAllocatingTests {
DataBufferUtils.readByteChannel(() -> channel, super.bufferFactory, 3);
StepVerifier.create(result)
.consumeNextWith(stringConsumer(""))
.consumeNextWith(stringConsumer("foo"))
.expectError(IOException.class)
.verify(Duration.ofSeconds(3));
}
@@ -172,18 +171,19 @@ class DataBufferUtilsTests extends AbstractDataBufferAllocatingTests {
byteBuffer.put("foo".getBytes(StandardCharsets.UTF_8));
long pos = invocation.getArgument(1);
assertThat(pos).isEqualTo(0);
CompletionHandler<Integer, ByteBuffer> completionHandler = invocation.getArgument(3);
completionHandler.completed(3, byteBuffer);
Object attachment = invocation.getArgument(2);
CompletionHandler<Integer, Object> completionHandler = invocation.getArgument(3);
completionHandler.completed(3, attachment);
return null;
}).willAnswer(invocation -> {
ByteBuffer byteBuffer = invocation.getArgument(0);
CompletionHandler<Integer, ByteBuffer> completionHandler = invocation.getArgument(3);
completionHandler.failed(new IOException(), byteBuffer);
Object attachment = invocation.getArgument(2);
CompletionHandler<Integer, Object> completionHandler = invocation.getArgument(3);
completionHandler.failed(new IOException(), attachment);
return null;
})
.given(channel).read(any(), anyLong(), any(), any());
Flux<DataBuffer> result =
Flux<DataBuffer> result=
DataBufferUtils.readAsynchronousFileChannel(() -> channel, super.bufferFactory, 3);
StepVerifier.create(result)
@@ -474,24 +474,21 @@ class DataBufferUtilsTests extends AbstractDataBufferAllocatingTests {
willAnswer(invocation -> {
ByteBuffer buffer = invocation.getArgument(0);
long pos = invocation.getArgument(1);
CompletionHandler<Integer, ByteBuffer> completionHandler = invocation.getArgument(3);
assertThat(pos).isEqualTo(0);
Object attachment = invocation.getArgument(2);
CompletionHandler<Integer, Object> completionHandler = invocation.getArgument(3);
int written = buffer.remaining();
buffer.position(buffer.limit());
completionHandler.completed(written, buffer);
completionHandler.completed(written, attachment);
return null;
})
.willAnswer(invocation -> {
ByteBuffer buffer = invocation.getArgument(0);
CompletionHandler<Integer, ByteBuffer> completionHandler =
invocation.getArgument(3);
completionHandler.failed(new IOException(), buffer);
Object attachment = invocation.getArgument(2);
CompletionHandler<Integer, Object> completionHandler = invocation.getArgument(3);
completionHandler.failed(new IOException(), attachment);
return null;
})
.given(channel).write(isA(ByteBuffer.class), anyLong(), isA(ByteBuffer.class), isA(CompletionHandler.class));
.given(channel).write(any(), anyLong(), any(), any());
Flux<DataBuffer> writeResult = DataBufferUtils.write(flux, channel);
StepVerifier.create(writeResult)