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.
@@ -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;

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,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();
}
}

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.
@@ -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) {}
}
}

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.
@@ -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();

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.
@@ -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() {
}
}
}

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.
@@ -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();
}
}
}

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.
@@ -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);
}
}

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.
@@ -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() {
}
}
}

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.
@@ -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);
}
}