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:
@@ -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.
|
||||
@@ -51,9 +51,11 @@ public class ByteBufferDecoder extends AbstractDataBufferDecoder<ByteBuffer> {
|
||||
public ByteBuffer decode(DataBuffer dataBuffer, ResolvableType elementType,
|
||||
@Nullable MimeType mimeType, @Nullable Map<String, Object> hints) {
|
||||
|
||||
ByteBuffer result = dataBuffer.toByteBuffer();
|
||||
int len = dataBuffer.readableByteCount();
|
||||
ByteBuffer result = ByteBuffer.allocate(len);
|
||||
dataBuffer.toByteBuffer(result);
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug(Hints.getLogPrefix(hints) + "Read " + dataBuffer.readableByteCount() + " bytes");
|
||||
logger.debug(Hints.getLogPrefix(hints) + "Read " + len + " bytes");
|
||||
}
|
||||
DataBufferUtils.release(dataBuffer);
|
||||
return result;
|
||||
|
||||
@@ -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,14 +16,18 @@
|
||||
|
||||
package org.springframework.core.io.buffer;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.CharBuffer;
|
||||
import java.nio.charset.CharacterCodingException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.CharsetEncoder;
|
||||
import java.nio.charset.CoderResult;
|
||||
import java.nio.charset.CodingErrorAction;
|
||||
import java.util.Iterator;
|
||||
import java.util.function.IntPredicate;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
@@ -265,27 +269,28 @@ public interface DataBuffer {
|
||||
CharsetEncoder charsetEncoder = charset.newEncoder()
|
||||
.onMalformedInput(CodingErrorAction.REPLACE)
|
||||
.onUnmappableCharacter(CodingErrorAction.REPLACE);
|
||||
CharBuffer inBuffer = CharBuffer.wrap(charSequence);
|
||||
int estimatedSize = (int) (inBuffer.remaining() * charsetEncoder.averageBytesPerChar());
|
||||
ByteBuffer outBuffer = ensureCapacity(estimatedSize)
|
||||
.asByteBuffer(writePosition(), writableByteCount());
|
||||
while (true) {
|
||||
CoderResult cr = (inBuffer.hasRemaining() ?
|
||||
charsetEncoder.encode(inBuffer, outBuffer, true) : CoderResult.UNDERFLOW);
|
||||
if (cr.isUnderflow()) {
|
||||
cr = charsetEncoder.flush(outBuffer);
|
||||
CharBuffer src = CharBuffer.wrap(charSequence);
|
||||
int length = (int) (src.remaining() * charsetEncoder.maxBytesPerChar());
|
||||
ensureWritable(length);
|
||||
try (ByteBufferIterator iterator = writableByteBuffers()) {
|
||||
Assert.state(iterator.hasNext(), "No ByteBuffer available");
|
||||
ByteBuffer dest = iterator.next();
|
||||
int pos = dest.position();
|
||||
CoderResult cr = charsetEncoder.encode(src, dest, true);
|
||||
if (!cr.isUnderflow()) {
|
||||
cr.throwException();
|
||||
}
|
||||
if (cr.isUnderflow()) {
|
||||
break;
|
||||
}
|
||||
if (cr.isOverflow()) {
|
||||
writePosition(writePosition() + outBuffer.position());
|
||||
int maximumSize = (int) (inBuffer.remaining() * charsetEncoder.maxBytesPerChar());
|
||||
ensureCapacity(maximumSize);
|
||||
outBuffer = asByteBuffer(writePosition(), writableByteCount());
|
||||
cr = charsetEncoder.flush(dest);
|
||||
if (!cr.isUnderflow()) {
|
||||
cr.throwException();
|
||||
}
|
||||
length = dest.position() - pos;
|
||||
}
|
||||
writePosition(writePosition() + outBuffer.position());
|
||||
catch (CharacterCodingException ex) {
|
||||
// should not happen, because the encoder uses action REPLACE
|
||||
throw new UncheckedIOException(ex);
|
||||
}
|
||||
writePosition(writePosition() + length);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
@@ -353,8 +358,8 @@ public interface DataBuffer {
|
||||
* changes in the returned buffer's {@linkplain ByteBuffer#position() position}
|
||||
* will not be reflected in the reading nor writing position of this data buffer.
|
||||
* @return this data buffer as a byte buffer
|
||||
* @deprecated as of 6.0, in favor of {@link #toByteBuffer()}, which does
|
||||
* <strong>not</strong> share data and returns a copy.
|
||||
* @deprecated as of 6.0, in favor of {@link #toByteBuffer(ByteBuffer)},
|
||||
* {@link #readableByteBuffers()}, or {@link #writableByteBuffers()}.
|
||||
*/
|
||||
@Deprecated(since = "6.0")
|
||||
ByteBuffer asByteBuffer();
|
||||
@@ -368,8 +373,8 @@ public interface DataBuffer {
|
||||
* @param length the length of the returned byte buffer
|
||||
* @return this data buffer as a byte buffer
|
||||
* @since 5.0.1
|
||||
* @deprecated as of 6.0, in favor of {@link #toByteBuffer(int, int)}, which
|
||||
* does <strong>not</strong> share data and returns a copy.
|
||||
* @deprecated as of 6.0, in favor of {@link #toByteBuffer(int, ByteBuffer, int, int)},
|
||||
* {@link #readableByteBuffers()}, or {@link #writableByteBuffers()}.
|
||||
*/
|
||||
@Deprecated(since = "6.0")
|
||||
ByteBuffer asByteBuffer(int index, int length);
|
||||
@@ -380,7 +385,11 @@ public interface DataBuffer {
|
||||
* <strong>not</strong> shared.
|
||||
* @return this data buffer as a byte buffer
|
||||
* @since 6.0
|
||||
* @see #readableByteBuffers()
|
||||
* @see #writableByteBuffers()
|
||||
* @deprecated as of 6.0.5, in favor of {@link #toByteBuffer(ByteBuffer)}
|
||||
*/
|
||||
@Deprecated(since = "6.0.5")
|
||||
default ByteBuffer toByteBuffer() {
|
||||
return toByteBuffer(readPosition(), readableByteCount());
|
||||
}
|
||||
@@ -391,9 +400,67 @@ public interface DataBuffer {
|
||||
* {@code ByteBuffer} is <strong>not</strong> shared.
|
||||
* @return this data buffer as a byte buffer
|
||||
* @since 6.0
|
||||
* @see #readableByteBuffers()
|
||||
* @see #writableByteBuffers()
|
||||
* @deprecated as of 6.0.5, in favor of
|
||||
* {@link #toByteBuffer(int, ByteBuffer, int, int)}
|
||||
*/
|
||||
@Deprecated(since = "6.0.5")
|
||||
ByteBuffer toByteBuffer(int index, int length);
|
||||
|
||||
/**
|
||||
* Copies this entire data buffer into the given destination
|
||||
* {@code ByteBuffer}, beginning at the current
|
||||
* {@linkplain #readPosition() reading position}, and the current
|
||||
* {@linkplain ByteBuffer#position() position} of destination byte buffer.
|
||||
* @param dest the destination byte buffer
|
||||
* @since 6.0.5
|
||||
*/
|
||||
default void toByteBuffer(ByteBuffer dest) {
|
||||
toByteBuffer(readPosition(), dest, dest.position(), readableByteCount());
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies the given length from this data buffer into the given destination
|
||||
* {@code ByteBuffer}, beginning at the given source position, and the
|
||||
* given destination position in the destination byte buffer.
|
||||
* @param srcPos the position of this data buffer from where copying should
|
||||
* start
|
||||
* @param dest the destination byte buffer
|
||||
* @param destPos the position in {@code dest} to where copying should
|
||||
* start
|
||||
* @param length the amount of data to copy
|
||||
* @since 6.0.5
|
||||
*/
|
||||
void toByteBuffer(int srcPos, ByteBuffer dest, int destPos, int length);
|
||||
|
||||
/**
|
||||
* Returns a closeable iterator over each {@link ByteBuffer} in this data
|
||||
* buffer that can be read. Calling this method is more efficient than
|
||||
* {@link #toByteBuffer()}, as no data is copied. However, the byte buffers
|
||||
* provided can only be used during the iteration.
|
||||
*
|
||||
* <p><b>Note</b> that the returned iterator must be used in a
|
||||
* try-with-resources clause or explicitly
|
||||
* {@linkplain ByteBufferIterator#close() closed}.
|
||||
* @return a closeable iterator over the readable byte buffers contained in this data buffer
|
||||
* @since 6.0.5
|
||||
*/
|
||||
ByteBufferIterator readableByteBuffers();
|
||||
|
||||
/**
|
||||
* Returns a closeable iterator over each {@link ByteBuffer} in this data
|
||||
* buffer that can be written to. The byte buffers provided can only be used
|
||||
* during the iteration.
|
||||
*
|
||||
* <p><b>Note</b> that the returned iterator must be used in a
|
||||
* try-with-resources clause or explicitly
|
||||
* {@linkplain ByteBufferIterator#close() closed}.
|
||||
* @return a closeable iterator over the writable byte buffers contained in this data buffer
|
||||
* @since 6.0.5
|
||||
*/
|
||||
ByteBufferIterator writableByteBuffers();
|
||||
|
||||
/**
|
||||
* Expose this buffer's data as an {@link InputStream}. Both data and read position are
|
||||
* shared between the returned stream and this data buffer. The underlying buffer will
|
||||
@@ -450,4 +517,20 @@ public interface DataBuffer {
|
||||
*/
|
||||
String toString(int index, int length, Charset charset);
|
||||
|
||||
|
||||
/**
|
||||
* A dedicated iterator type that ensures the lifecycle of iterated
|
||||
* {@link ByteBuffer} elements. This iterator must be used in a
|
||||
* try-with-resources clause or explicitly {@linkplain #close() closed}.
|
||||
*
|
||||
* @see DataBuffer#readableByteBuffers()
|
||||
* @see DataBuffer#writableByteBuffers()
|
||||
*/
|
||||
interface ByteBufferIterator extends Iterator<ByteBuffer>, Closeable {
|
||||
|
||||
@Override
|
||||
void close();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
@@ -906,13 +906,16 @@ public abstract class DataBufferUtils {
|
||||
|
||||
@Override
|
||||
public void accept(SynchronousSink<DataBuffer> sink) {
|
||||
ByteBuffer byteBuffer = this.dataBufferFactory.isDirect() ?
|
||||
ByteBuffer.allocateDirect(this.bufferSize) :
|
||||
ByteBuffer.allocate(this.bufferSize);
|
||||
int read = -1;
|
||||
DataBuffer dataBuffer = this.dataBufferFactory.allocateBuffer(this.bufferSize);
|
||||
try {
|
||||
if (this.channel.read(byteBuffer) >= 0) {
|
||||
byteBuffer.flip();
|
||||
DataBuffer dataBuffer = this.dataBufferFactory.wrap(byteBuffer);
|
||||
try (DataBuffer.ByteBufferIterator iterator = dataBuffer.writableByteBuffers()) {
|
||||
Assert.state(iterator.hasNext(), "No ByteBuffer available");
|
||||
ByteBuffer byteBuffer = iterator.next();
|
||||
read = this.channel.read(byteBuffer);
|
||||
}
|
||||
if (read >= 0) {
|
||||
dataBuffer.writePosition(read);
|
||||
sink.next(dataBuffer);
|
||||
}
|
||||
else {
|
||||
@@ -922,11 +925,16 @@ public abstract class DataBufferUtils {
|
||||
catch (IOException ex) {
|
||||
sink.error(ex);
|
||||
}
|
||||
finally {
|
||||
if (read == -1) {
|
||||
release(dataBuffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class ReadCompletionHandler implements CompletionHandler<Integer, ByteBuffer> {
|
||||
private static class ReadCompletionHandler implements CompletionHandler<Integer, ReadCompletionHandler.Attachment> {
|
||||
|
||||
private final AsynchronousFileChannel channel;
|
||||
|
||||
@@ -978,20 +986,27 @@ public abstract class DataBufferUtils {
|
||||
}
|
||||
|
||||
private void read() {
|
||||
ByteBuffer byteBuffer = this.dataBufferFactory.isDirect() ?
|
||||
ByteBuffer.allocateDirect(this.bufferSize) :
|
||||
ByteBuffer.allocate(this.bufferSize);
|
||||
this.channel.read(byteBuffer, this.position.get(), byteBuffer, this);
|
||||
DataBuffer dataBuffer = this.dataBufferFactory.allocateBuffer(this.bufferSize);
|
||||
DataBuffer.ByteBufferIterator iterator = dataBuffer.writableByteBuffers();
|
||||
Assert.state(iterator.hasNext(), "No ByteBuffer available");
|
||||
ByteBuffer byteBuffer = iterator.next();
|
||||
Attachment attachment = new Attachment(dataBuffer, iterator);
|
||||
this.channel.read(byteBuffer, this.position.get(), attachment, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void completed(Integer read, ByteBuffer byteBuffer) {
|
||||
public void completed(Integer read, Attachment attachment) {
|
||||
attachment.iterator().close();
|
||||
DataBuffer dataBuffer = attachment.dataBuffer();
|
||||
|
||||
if (this.state.get().equals(State.DISPOSED)) {
|
||||
release(dataBuffer);
|
||||
closeChannel(this.channel);
|
||||
return;
|
||||
}
|
||||
|
||||
if (read == -1) {
|
||||
release(dataBuffer);
|
||||
closeChannel(this.channel);
|
||||
this.state.set(State.DISPOSED);
|
||||
this.sink.complete();
|
||||
@@ -999,9 +1014,7 @@ public abstract class DataBufferUtils {
|
||||
}
|
||||
|
||||
this.position.addAndGet(read);
|
||||
|
||||
byteBuffer.flip();
|
||||
DataBuffer dataBuffer = this.dataBufferFactory.wrap(byteBuffer);
|
||||
dataBuffer.writePosition(read);
|
||||
this.sink.next(dataBuffer);
|
||||
|
||||
// Stay in READING mode if there is demand
|
||||
@@ -1017,7 +1030,10 @@ public abstract class DataBufferUtils {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failed(Throwable exc, ByteBuffer byteBuffer) {
|
||||
public void failed(Throwable exc, Attachment attachment) {
|
||||
attachment.iterator().close();
|
||||
release(attachment.dataBuffer());
|
||||
|
||||
closeChannel(this.channel);
|
||||
this.state.set(State.DISPOSED);
|
||||
this.sink.error(exc);
|
||||
@@ -1026,6 +1042,8 @@ public abstract class DataBufferUtils {
|
||||
private enum State {
|
||||
IDLE, READING, DISPOSED
|
||||
}
|
||||
|
||||
private record Attachment(DataBuffer dataBuffer, DataBuffer.ByteBufferIterator iterator) {}
|
||||
}
|
||||
|
||||
|
||||
@@ -1048,9 +1066,11 @@ public abstract class DataBufferUtils {
|
||||
@Override
|
||||
protected void hookOnNext(DataBuffer dataBuffer) {
|
||||
try {
|
||||
ByteBuffer byteBuffer = dataBuffer.toByteBuffer();
|
||||
while (byteBuffer.hasRemaining()) {
|
||||
this.channel.write(byteBuffer);
|
||||
try (DataBuffer.ByteBufferIterator iterator = dataBuffer.readableByteBuffers()) {
|
||||
ByteBuffer byteBuffer = iterator.next();
|
||||
while (byteBuffer.hasRemaining()) {
|
||||
this.channel.write(byteBuffer);
|
||||
}
|
||||
}
|
||||
this.sink.next(dataBuffer);
|
||||
request(1);
|
||||
@@ -1080,20 +1100,20 @@ public abstract class DataBufferUtils {
|
||||
|
||||
|
||||
private static class WriteCompletionHandler extends BaseSubscriber<DataBuffer>
|
||||
implements CompletionHandler<Integer, ByteBuffer> {
|
||||
implements CompletionHandler<Integer, WriteCompletionHandler.Attachment> {
|
||||
|
||||
private final FluxSink<DataBuffer> sink;
|
||||
|
||||
private final AsynchronousFileChannel channel;
|
||||
|
||||
private final AtomicBoolean writing = new AtomicBoolean();
|
||||
|
||||
private final AtomicBoolean completed = new AtomicBoolean();
|
||||
|
||||
private final AtomicReference<Throwable> error = new AtomicReference<>();
|
||||
|
||||
private final AtomicLong position;
|
||||
|
||||
private final AtomicReference<DataBuffer> dataBuffer = new AtomicReference<>();
|
||||
|
||||
public WriteCompletionHandler(
|
||||
FluxSink<DataBuffer> sink, AsynchronousFileChannel channel, long position) {
|
||||
|
||||
@@ -1108,19 +1128,22 @@ public abstract class DataBufferUtils {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void hookOnNext(DataBuffer value) {
|
||||
if (!this.dataBuffer.compareAndSet(null, value)) {
|
||||
throw new IllegalStateException();
|
||||
protected void hookOnNext(DataBuffer dataBuffer) {
|
||||
DataBuffer.ByteBufferIterator iterator = dataBuffer.readableByteBuffers();
|
||||
if (iterator.hasNext()) {
|
||||
ByteBuffer byteBuffer = iterator.next();
|
||||
long pos = this.position.get();
|
||||
Attachment attachment = new Attachment(byteBuffer, dataBuffer, iterator);
|
||||
this.writing.set(true);
|
||||
this.channel.write(byteBuffer, pos, attachment, this);
|
||||
}
|
||||
ByteBuffer byteBuffer = value.toByteBuffer();
|
||||
this.channel.write(byteBuffer, this.position.get(), byteBuffer, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void hookOnError(Throwable throwable) {
|
||||
this.error.set(throwable);
|
||||
|
||||
if (this.dataBuffer.get() == null) {
|
||||
if (!this.writing.get()) {
|
||||
this.sink.error(throwable);
|
||||
}
|
||||
}
|
||||
@@ -1129,43 +1152,55 @@ public abstract class DataBufferUtils {
|
||||
protected void hookOnComplete() {
|
||||
this.completed.set(true);
|
||||
|
||||
if (this.dataBuffer.get() == null) {
|
||||
if (!this.writing.get()) {
|
||||
this.sink.complete();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void completed(Integer written, ByteBuffer byteBuffer) {
|
||||
long pos = this.position.addAndGet(written);
|
||||
if (byteBuffer.hasRemaining()) {
|
||||
this.channel.write(byteBuffer, pos, byteBuffer, this);
|
||||
return;
|
||||
}
|
||||
sinkDataBuffer();
|
||||
public void completed(Integer written, Attachment attachment) {
|
||||
this.writing.set(false);
|
||||
attachment.iterator().close();
|
||||
|
||||
Throwable throwable = this.error.get();
|
||||
if (throwable != null) {
|
||||
this.sink.error(throwable);
|
||||
long pos = this.position.addAndGet(written);
|
||||
ByteBuffer byteBuffer = attachment.byteBuffer();
|
||||
DataBuffer.ByteBufferIterator iterator = attachment.iterator();
|
||||
if (byteBuffer.hasRemaining()) {
|
||||
this.writing.set(true);
|
||||
this.channel.write(byteBuffer, pos, attachment, this);
|
||||
}
|
||||
else if (this.completed.get()) {
|
||||
this.sink.complete();
|
||||
else if (iterator.hasNext()) {
|
||||
ByteBuffer next = iterator.next();
|
||||
this.writing.set(true);
|
||||
this.channel.write(next, pos, attachment, this);
|
||||
}
|
||||
else {
|
||||
request(1);
|
||||
sinkDataBuffer(attachment.dataBuffer());
|
||||
|
||||
Throwable throwable = this.error.get();
|
||||
if (throwable != null) {
|
||||
this.sink.error(throwable);
|
||||
}
|
||||
else if (this.completed.get()) {
|
||||
this.sink.complete();
|
||||
}
|
||||
else {
|
||||
request(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failed(Throwable exc, ByteBuffer byteBuffer) {
|
||||
sinkDataBuffer();
|
||||
public void failed(Throwable exc, Attachment attachment) {
|
||||
this.writing.set(false);
|
||||
attachment.iterator().close();
|
||||
|
||||
sinkDataBuffer(attachment.dataBuffer());
|
||||
this.sink.error(exc);
|
||||
}
|
||||
|
||||
private void sinkDataBuffer() {
|
||||
DataBuffer dataBuffer = this.dataBuffer.get();
|
||||
Assert.state(dataBuffer != null, "DataBuffer should not be null");
|
||||
private void sinkDataBuffer(DataBuffer dataBuffer) {
|
||||
this.sink.next(dataBuffer);
|
||||
this.dataBuffer.set(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1173,6 +1208,10 @@ public abstract class DataBufferUtils {
|
||||
return Context.of(this.sink.contextView());
|
||||
}
|
||||
|
||||
private record Attachment(ByteBuffer byteBuffer, DataBuffer dataBuffer, DataBuffer.ByteBufferIterator iterator) {}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
@@ -202,15 +202,37 @@ public class DataBufferWrapper implements DataBuffer {
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public ByteBuffer toByteBuffer() {
|
||||
return this.delegate.toByteBuffer();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public ByteBuffer toByteBuffer(int index, int length) {
|
||||
return this.delegate.toByteBuffer(index, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toByteBuffer(ByteBuffer dest) {
|
||||
this.delegate.toByteBuffer(dest);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toByteBuffer(int srcPos, ByteBuffer dest, int destPos, int length) {
|
||||
this.delegate.toByteBuffer(srcPos, dest, destPos, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBufferIterator readableByteBuffers() {
|
||||
return this.delegate.readableByteBuffers();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBufferIterator writableByteBuffers() {
|
||||
return this.delegate.writableByteBuffers();
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream asInputStream() {
|
||||
return this.delegate.asInputStream();
|
||||
|
||||
@@ -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.
|
||||
@@ -19,6 +19,7 @@ package org.springframework.core.io.buffer;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Arrays;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.function.IntPredicate;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
@@ -304,9 +305,14 @@ public class DefaultDataBuffer implements DataBuffer {
|
||||
}
|
||||
|
||||
@Override
|
||||
public DefaultDataBuffer write(DataBuffer... buffers) {
|
||||
if (!ObjectUtils.isEmpty(buffers)) {
|
||||
write(Arrays.stream(buffers).map(DataBuffer::toByteBuffer).toArray(ByteBuffer[]::new));
|
||||
public DefaultDataBuffer write(DataBuffer... dataBuffers) {
|
||||
if (!ObjectUtils.isEmpty(dataBuffers)) {
|
||||
ByteBuffer[] byteBuffers = new ByteBuffer[dataBuffers.length];
|
||||
for (int i = 0; i < dataBuffers.length; i++) {
|
||||
byteBuffers[i] = ByteBuffer.allocate(dataBuffers[i].readableByteCount());
|
||||
dataBuffers[i].toByteBuffer(byteBuffers[i]);
|
||||
}
|
||||
write(byteBuffers);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
@@ -388,6 +394,7 @@ public class DefaultDataBuffer implements DataBuffer {
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public ByteBuffer toByteBuffer(int index, int length) {
|
||||
checkIndex(index, length);
|
||||
|
||||
@@ -398,6 +405,29 @@ public class DefaultDataBuffer implements DataBuffer {
|
||||
return copy.flip();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toByteBuffer(int srcPos, ByteBuffer dest, int destPos, int length) {
|
||||
checkIndex(srcPos, length);
|
||||
Assert.notNull(dest, "Dest must not be null");
|
||||
|
||||
dest = dest.duplicate().clear();
|
||||
dest.put(destPos, this.byteBuffer, srcPos, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataBuffer.ByteBufferIterator readableByteBuffers() {
|
||||
ByteBuffer readOnly = this.byteBuffer.asReadOnlyBuffer();
|
||||
readOnly.clear().position(this.readPosition).limit(this.writePosition - this.readPosition);
|
||||
return new ByteBufferIterator(readOnly);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataBuffer.ByteBufferIterator writableByteBuffers() {
|
||||
ByteBuffer duplicate = this.byteBuffer.duplicate();
|
||||
duplicate.clear().position(this.writePosition).limit(this.capacity - this.writePosition);
|
||||
return new ByteBufferIterator(duplicate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(int index, int length, Charset charset) {
|
||||
checkIndex(index, length);
|
||||
@@ -512,4 +542,37 @@ public class DefaultDataBuffer implements DataBuffer {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static final class ByteBufferIterator implements DataBuffer.ByteBufferIterator {
|
||||
|
||||
private final ByteBuffer buffer;
|
||||
|
||||
private boolean hasNext = true;
|
||||
|
||||
|
||||
public ByteBufferIterator(ByteBuffer buffer) {
|
||||
this.buffer = buffer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return this.hasNext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer next() {
|
||||
if (!this.hasNext) {
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
else {
|
||||
this.hasNext = false;
|
||||
return this.buffer;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
@@ -18,11 +18,12 @@ package org.springframework.core.io.buffer;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.function.IntPredicate;
|
||||
|
||||
import io.netty5.buffer.Buffer;
|
||||
import io.netty5.util.AsciiString;
|
||||
import io.netty5.buffer.BufferComponent;
|
||||
import io.netty5.buffer.ComponentIterator;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -188,20 +189,20 @@ public final class Netty5DataBuffer implements CloseableDataBuffer, TouchableDat
|
||||
}
|
||||
|
||||
@Override
|
||||
public Netty5DataBuffer write(DataBuffer... buffers) {
|
||||
if (!ObjectUtils.isEmpty(buffers)) {
|
||||
if (hasNetty5DataBuffers(buffers)) {
|
||||
Buffer[] nativeBuffers = new Buffer[buffers.length];
|
||||
for (int i = 0; i < buffers.length; i++) {
|
||||
nativeBuffers[i] = ((Netty5DataBuffer) buffers[i]).getNativeBuffer();
|
||||
public Netty5DataBuffer write(DataBuffer... dataBuffers) {
|
||||
if (!ObjectUtils.isEmpty(dataBuffers)) {
|
||||
if (hasNetty5DataBuffers(dataBuffers)) {
|
||||
Buffer[] nativeBuffers = new Buffer[dataBuffers.length];
|
||||
for (int i = 0; i < dataBuffers.length; i++) {
|
||||
nativeBuffers[i] = ((Netty5DataBuffer) dataBuffers[i]).getNativeBuffer();
|
||||
}
|
||||
return write(nativeBuffers);
|
||||
}
|
||||
else {
|
||||
ByteBuffer[] byteBuffers = new ByteBuffer[buffers.length];
|
||||
for (int i = 0; i < buffers.length; i++) {
|
||||
byteBuffers[i] = buffers[i].toByteBuffer();
|
||||
|
||||
ByteBuffer[] byteBuffers = new ByteBuffer[dataBuffers.length];
|
||||
for (int i = 0; i < dataBuffers.length; i++) {
|
||||
byteBuffers[i] = ByteBuffer.allocate(dataBuffers[i].readableByteCount());
|
||||
dataBuffers[i].toByteBuffer(byteBuffers[i]);
|
||||
}
|
||||
return write(byteBuffers);
|
||||
}
|
||||
@@ -248,13 +249,7 @@ public final class Netty5DataBuffer implements CloseableDataBuffer, TouchableDat
|
||||
Assert.notNull(charSequence, "CharSequence must not be null");
|
||||
Assert.notNull(charset, "Charset must not be null");
|
||||
|
||||
if (StandardCharsets.US_ASCII.equals(charset) && charSequence instanceof AsciiString asciiString) {
|
||||
this.buffer.writeBytes(asciiString.array(), asciiString.arrayOffset(), asciiString.length());
|
||||
}
|
||||
else {
|
||||
byte[] bytes = charSequence.toString().getBytes(charset);
|
||||
this.buffer.writeBytes(bytes);
|
||||
}
|
||||
this.buffer.writeCharSequence(charSequence, charset);
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -300,6 +295,21 @@ public final class Netty5DataBuffer implements CloseableDataBuffer, TouchableDat
|
||||
return copy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toByteBuffer(int srcPos, ByteBuffer dest, int destPos, int length) {
|
||||
this.buffer.copyInto(srcPos, dest, destPos, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBufferIterator readableByteBuffers() {
|
||||
return new BufferComponentIterator<>(this.buffer.forEachComponent(), true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBufferIterator writableByteBuffers() {
|
||||
return new BufferComponentIterator<>(this.buffer.forEachComponent(), false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(Charset charset) {
|
||||
Assert.notNull(charset, "Charset must not be null");
|
||||
@@ -342,4 +352,53 @@ public final class Netty5DataBuffer implements CloseableDataBuffer, TouchableDat
|
||||
return this.buffer.toString();
|
||||
}
|
||||
|
||||
|
||||
private static final class BufferComponentIterator<T extends BufferComponent & ComponentIterator.Next>
|
||||
implements ByteBufferIterator {
|
||||
|
||||
private final ComponentIterator<T> delegate;
|
||||
|
||||
private final boolean readable;
|
||||
|
||||
@Nullable
|
||||
private T next;
|
||||
|
||||
|
||||
public BufferComponentIterator(ComponentIterator<T> delegate, boolean readable) {
|
||||
Assert.notNull(delegate, "Delegate must not be null");
|
||||
this.delegate = delegate;
|
||||
this.readable = readable;
|
||||
this.next = readable ? this.delegate.firstReadable() : this.delegate.firstWritable();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return this.next != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer next() {
|
||||
if (this.next != null) {
|
||||
ByteBuffer result;
|
||||
if (this.readable) {
|
||||
result = this.next.readableBuffer();
|
||||
this.next = this.next.nextReadable();
|
||||
}
|
||||
else {
|
||||
result = this.next.writableBuffer();
|
||||
this.next = this.next.nextWritable();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
else {
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
this.delegate.close();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
@@ -127,7 +127,9 @@ public class Netty5DataBufferFactory implements DataBufferFactory {
|
||||
return netty5DataBuffer.getNativeBuffer();
|
||||
}
|
||||
else {
|
||||
return DefaultBufferAllocators.preferredAllocator().copyOf(buffer.toByteBuffer());
|
||||
ByteBuffer byteBuffer = ByteBuffer.allocate(buffer.readableByteCount());
|
||||
buffer.toByteBuffer(byteBuffer);
|
||||
return DefaultBufferAllocators.preferredAllocator().copyOf(byteBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
@@ -19,6 +19,7 @@ package org.springframework.core.io.buffer;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.function.IntPredicate;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
@@ -182,19 +183,20 @@ public class NettyDataBuffer implements PooledDataBuffer {
|
||||
}
|
||||
|
||||
@Override
|
||||
public NettyDataBuffer write(DataBuffer... buffers) {
|
||||
if (!ObjectUtils.isEmpty(buffers)) {
|
||||
if (hasNettyDataBuffers(buffers)) {
|
||||
ByteBuf[] nativeBuffers = new ByteBuf[buffers.length];
|
||||
for (int i = 0; i < buffers.length; i++) {
|
||||
nativeBuffers[i] = ((NettyDataBuffer) buffers[i]).getNativeBuffer();
|
||||
public NettyDataBuffer write(DataBuffer... dataBuffers) {
|
||||
if (!ObjectUtils.isEmpty(dataBuffers)) {
|
||||
if (hasNettyDataBuffers(dataBuffers)) {
|
||||
ByteBuf[] nativeBuffers = new ByteBuf[dataBuffers.length];
|
||||
for (int i = 0; i < dataBuffers.length; i++) {
|
||||
nativeBuffers[i] = ((NettyDataBuffer) dataBuffers[i]).getNativeBuffer();
|
||||
}
|
||||
write(nativeBuffers);
|
||||
}
|
||||
else {
|
||||
ByteBuffer[] byteBuffers = new ByteBuffer[buffers.length];
|
||||
for (int i = 0; i < buffers.length; i++) {
|
||||
byteBuffers[i] = buffers[i].toByteBuffer();
|
||||
ByteBuffer[] byteBuffers = new ByteBuffer[dataBuffers.length];
|
||||
for (int i = 0; i < dataBuffers.length; i++) {
|
||||
byteBuffers[i] = ByteBuffer.allocate(dataBuffers[i].readableByteCount());
|
||||
dataBuffers[i].toByteBuffer(byteBuffers[i]);
|
||||
}
|
||||
write(byteBuffers);
|
||||
}
|
||||
@@ -295,6 +297,7 @@ public class NettyDataBuffer implements PooledDataBuffer {
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public ByteBuffer toByteBuffer(int index, int length) {
|
||||
ByteBuffer result = this.byteBuf.isDirect() ?
|
||||
ByteBuffer.allocateDirect(length) :
|
||||
@@ -305,6 +308,26 @@ public class NettyDataBuffer implements PooledDataBuffer {
|
||||
return result.flip();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toByteBuffer(int srcPos, ByteBuffer dest, int destPos, int length) {
|
||||
Assert.notNull(dest, "Dest must not be null");
|
||||
|
||||
dest = dest.duplicate().clear();
|
||||
dest.put(destPos, this.byteBuf.nioBuffer(), srcPos, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataBuffer.ByteBufferIterator readableByteBuffers() {
|
||||
ByteBuffer[] readable = this.byteBuf.nioBuffers(this.byteBuf.readerIndex(), this.byteBuf.readableBytes());
|
||||
return new ByteBufferIterator(readable, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataBuffer.ByteBufferIterator writableByteBuffers() {
|
||||
ByteBuffer[] writable = this.byteBuf.nioBuffers(this.byteBuf.writerIndex(), this.byteBuf.writableBytes());
|
||||
return new ByteBufferIterator(writable, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(Charset charset) {
|
||||
Assert.notNull(charset, "Charset must not be null");
|
||||
@@ -355,4 +378,42 @@ public class NettyDataBuffer implements PooledDataBuffer {
|
||||
return this.byteBuf.toString();
|
||||
}
|
||||
|
||||
|
||||
private static final class ByteBufferIterator implements DataBuffer.ByteBufferIterator {
|
||||
|
||||
private final ByteBuffer[] byteBuffers;
|
||||
|
||||
private final boolean readOnly;
|
||||
|
||||
private int cursor = 0;
|
||||
|
||||
|
||||
public ByteBufferIterator(ByteBuffer[] byteBuffers, boolean readOnly) {
|
||||
this.byteBuffers = byteBuffers;
|
||||
this.readOnly = readOnly;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return this.cursor < this.byteBuffers.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer next() {
|
||||
int index = this.cursor;
|
||||
if (index < this.byteBuffers.length) {
|
||||
this.cursor = index + 1;
|
||||
ByteBuffer next = this.byteBuffers[index];
|
||||
return this.readOnly ? next.asReadOnlyBuffer() : next;
|
||||
}
|
||||
else {
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
@@ -122,17 +122,19 @@ public class NettyDataBufferFactory implements DataBufferFactory {
|
||||
/**
|
||||
* Return the given Netty {@link DataBuffer} as a {@link ByteBuf}.
|
||||
* <p>Returns the {@linkplain NettyDataBuffer#getNativeBuffer() native buffer}
|
||||
* if {@code buffer} is a {@link NettyDataBuffer}; returns
|
||||
* if {@code dataBuffer} is a {@link NettyDataBuffer}; returns
|
||||
* {@link Unpooled#wrappedBuffer(ByteBuffer)} otherwise.
|
||||
* @param buffer the {@code DataBuffer} to return a {@code ByteBuf} for
|
||||
* @param dataBuffer the {@code DataBuffer} to return a {@code ByteBuf} for
|
||||
* @return the netty {@code ByteBuf}
|
||||
*/
|
||||
public static ByteBuf toByteBuf(DataBuffer buffer) {
|
||||
if (buffer instanceof NettyDataBuffer nettyDataBuffer) {
|
||||
public static ByteBuf toByteBuf(DataBuffer dataBuffer) {
|
||||
if (dataBuffer instanceof NettyDataBuffer nettyDataBuffer) {
|
||||
return nettyDataBuffer.getNativeBuffer();
|
||||
}
|
||||
else {
|
||||
return Unpooled.wrappedBuffer(buffer.toByteBuffer());
|
||||
ByteBuffer byteBuffer = ByteBuffer.allocate(dataBuffer.readableByteCount());
|
||||
dataBuffer.toByteBuffer(byteBuffer);
|
||||
return Unpooled.wrappedBuffer(byteBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user