Introduce support for Netty 5 Buffer
This commit introduces support for Netty 5's Buffer, in the form of Netty5DataBuffer. Because of the new API offered by Buffer, several changes have been made to the DataBuffer API: - CloseableDataBuffer is a simpler alternative to PooledDataBuffer, and implemented by Netty5DataBuffer. DataBufferUtils::release can now handle CloseableDataBuffer as well as PooledDataBuffer. - PooledDataBuffer::touch has been moved into a separate interface: TouchableDataBuffer, which is implemented by Netty5DataBuffer. - The capacity of DataBuffers can no longer be reduced, they can only grow larger. As a consequence, DataBuffer::capacity(int) has been deprecated, but ensureWritable (formally ensureCapacity) still exists. - DataBuffer::slice and retainedSlice have been deprecated in favor of split, a new method that ensures that memory regions do not overlap. - DataBuffer::asByteBuffer has been deprecated in favor of toByteBuffer, a new method that returns a copy, instead of shared data. - DataBufferFactory::allocateBuffer has been deprecated in favor of allocateBuffer(int). Closes gh-28874
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2022 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.
|
||||
@@ -25,7 +25,6 @@ import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.io.buffer.DataBuffer;
|
||||
import org.springframework.core.io.buffer.DataBufferFactory;
|
||||
import org.springframework.core.io.buffer.DataBufferUtils;
|
||||
import org.springframework.core.io.buffer.PooledDataBuffer;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.MimeType;
|
||||
|
||||
@@ -52,7 +51,7 @@ public abstract class AbstractSingleValueEncoder<T> extends AbstractEncoder<T> {
|
||||
return Flux.from(inputStream)
|
||||
.take(1)
|
||||
.concatMap(value -> encode(value, bufferFactory, elementType, mimeType, hints))
|
||||
.doOnDiscard(PooledDataBuffer.class, DataBufferUtils::release);
|
||||
.doOnDiscard(DataBuffer.class, DataBufferUtils::release);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2022 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,15 +51,12 @@ public class ByteBufferDecoder extends AbstractDataBufferDecoder<ByteBuffer> {
|
||||
public ByteBuffer decode(DataBuffer dataBuffer, ResolvableType elementType,
|
||||
@Nullable MimeType mimeType, @Nullable Map<String, Object> hints) {
|
||||
|
||||
int byteCount = dataBuffer.readableByteCount();
|
||||
ByteBuffer copy = ByteBuffer.allocate(byteCount);
|
||||
copy.put(dataBuffer.asByteBuffer());
|
||||
copy.flip();
|
||||
DataBufferUtils.release(dataBuffer);
|
||||
ByteBuffer result = dataBuffer.toByteBuffer();
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug(Hints.getLogPrefix(hints) + "Read " + byteCount + " bytes");
|
||||
logger.debug(Hints.getLogPrefix(hints) + "Read " + dataBuffer.readableByteCount() + " bytes");
|
||||
}
|
||||
return copy;
|
||||
DataBufferUtils.release(dataBuffer);
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2022 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.
|
||||
@@ -35,7 +35,7 @@ import org.springframework.util.MimeTypeUtils;
|
||||
* after they have been consumed. In addition, if using {@code Flux} or
|
||||
* {@code Mono} operators such as flatMap, reduce, and others that prefetch,
|
||||
* cache, and skip or filter out data items internally, please add
|
||||
* {@code doOnDiscard(PooledDataBuffer.class, DataBufferUtils::release)} to the
|
||||
* {@code doOnDiscard(DataBuffer.class, DataBufferUtils::release)} to the
|
||||
* composition chain to ensure cached data buffers are released prior to an
|
||||
* error or cancellation signal.
|
||||
*
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright 2002-2022 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.core.codec;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import io.netty5.buffer.api.Buffer;
|
||||
import io.netty5.buffer.api.DefaultBufferAllocators;
|
||||
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.io.buffer.DataBuffer;
|
||||
import org.springframework.core.io.buffer.DataBufferUtils;
|
||||
import org.springframework.core.io.buffer.Netty5DataBuffer;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.MimeType;
|
||||
import org.springframework.util.MimeTypeUtils;
|
||||
|
||||
/**
|
||||
* Decoder for {@link Buffer Buffers}.
|
||||
*
|
||||
* @author Violeta Georgieva
|
||||
* @since 6.0
|
||||
*/
|
||||
public class Netty5BufferDecoder extends AbstractDataBufferDecoder<Buffer> {
|
||||
|
||||
public Netty5BufferDecoder() {
|
||||
super(MimeTypeUtils.ALL);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean canDecode(ResolvableType elementType, @Nullable MimeType mimeType) {
|
||||
return (Buffer.class.isAssignableFrom(elementType.toClass()) &&
|
||||
super.canDecode(elementType, mimeType));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Buffer decode(DataBuffer dataBuffer, ResolvableType elementType,
|
||||
@Nullable MimeType mimeType, @Nullable Map<String, Object> hints) {
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug(Hints.getLogPrefix(hints) + "Read " + dataBuffer.readableByteCount() + " bytes");
|
||||
}
|
||||
if (dataBuffer instanceof Netty5DataBuffer netty5DataBuffer) {
|
||||
return netty5DataBuffer.getNativeBuffer();
|
||||
}
|
||||
byte[] bytes = new byte[dataBuffer.readableByteCount()];
|
||||
dataBuffer.read(bytes);
|
||||
Buffer buffer = DefaultBufferAllocators.preferredAllocator().copyOf(bytes);
|
||||
DataBufferUtils.release(dataBuffer);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright 2002-2022 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.core.codec;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import io.netty5.buffer.api.Buffer;
|
||||
import org.reactivestreams.Publisher;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.io.buffer.DataBuffer;
|
||||
import org.springframework.core.io.buffer.DataBufferFactory;
|
||||
import org.springframework.core.io.buffer.Netty5DataBufferFactory;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.MimeType;
|
||||
import org.springframework.util.MimeTypeUtils;
|
||||
|
||||
/**
|
||||
* Encoder for {@link Buffer Buffers}.
|
||||
*
|
||||
* @author Violeta Georgieva
|
||||
* @since 6.0
|
||||
*/
|
||||
public class Netty5BufferEncoder extends AbstractEncoder<Buffer> {
|
||||
|
||||
public Netty5BufferEncoder() {
|
||||
super(MimeTypeUtils.ALL);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean canEncode(ResolvableType type, @Nullable MimeType mimeType) {
|
||||
Class<?> clazz = type.toClass();
|
||||
return super.canEncode(type, mimeType) && Buffer.class.isAssignableFrom(clazz);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<DataBuffer> encode(Publisher<? extends Buffer> inputStream,
|
||||
DataBufferFactory bufferFactory, ResolvableType elementType, @Nullable MimeType mimeType,
|
||||
@Nullable Map<String, Object> hints) {
|
||||
|
||||
return Flux.from(inputStream).map(byteBuffer ->
|
||||
encodeValue(byteBuffer, bufferFactory, elementType, mimeType, hints));
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataBuffer encodeValue(Buffer buffer, DataBufferFactory bufferFactory, ResolvableType valueType,
|
||||
@Nullable MimeType mimeType, @Nullable Map<String, Object> hints) {
|
||||
|
||||
if (logger.isDebugEnabled() && !Hints.isLoggingSuppressed(hints)) {
|
||||
String logPrefix = Hints.getLogPrefix(hints);
|
||||
logger.debug(logPrefix + "Writing " + buffer.readableBytes() + " bytes");
|
||||
}
|
||||
if (bufferFactory instanceof Netty5DataBufferFactory netty5DataBufferFactory) {
|
||||
return netty5DataBufferFactory.wrap(buffer);
|
||||
}
|
||||
byte[] bytes = new byte[buffer.readableBytes()];
|
||||
buffer.readBytes(bytes, 0, bytes.length);
|
||||
buffer.close();
|
||||
return bufferFactory.wrap(bytes);
|
||||
}
|
||||
}
|
||||
@@ -35,7 +35,6 @@ import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.io.buffer.DataBuffer;
|
||||
import org.springframework.core.io.buffer.DataBufferUtils;
|
||||
import org.springframework.core.io.buffer.LimitedDataBufferList;
|
||||
import org.springframework.core.io.buffer.PooledDataBuffer;
|
||||
import org.springframework.core.log.LogFormatUtils;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -127,7 +126,7 @@ public final class StringDecoder extends AbstractDataBufferDecoder<String> {
|
||||
return Mono.just(lastBuffer);
|
||||
}))
|
||||
.doOnTerminate(chunks::releaseAndClear)
|
||||
.doOnDiscard(PooledDataBuffer.class, PooledDataBuffer::release)
|
||||
.doOnDiscard(DataBuffer.class, DataBufferUtils::release)
|
||||
.map(buffer -> decode(buffer, elementType, mimeType, hints));
|
||||
}
|
||||
|
||||
@@ -153,26 +152,26 @@ public final class StringDecoder extends AbstractDataBufferDecoder<String> {
|
||||
DataBufferUtils.retain(buffer); // retain after add (may raise DataBufferLimitException)
|
||||
break;
|
||||
}
|
||||
int startIndex = buffer.readPosition();
|
||||
int length = (endIndex - startIndex + 1);
|
||||
DataBuffer slice = buffer.retainedSlice(startIndex, length);
|
||||
result = (result != null ? result : new ArrayList<>());
|
||||
DataBuffer split = buffer.split(endIndex + 1);
|
||||
if (result == null) {
|
||||
result = new ArrayList<>();
|
||||
}
|
||||
int delimiterLength = matcher.delimiter().length;
|
||||
if (chunks.isEmpty()) {
|
||||
if (this.stripDelimiter) {
|
||||
slice.writePosition(slice.writePosition() - matcher.delimiter().length);
|
||||
split.writePosition(split.writePosition() - delimiterLength);
|
||||
}
|
||||
result.add(slice);
|
||||
result.add(split);
|
||||
}
|
||||
else {
|
||||
chunks.add(slice);
|
||||
chunks.add(split);
|
||||
DataBuffer joined = buffer.factory().join(chunks);
|
||||
if (this.stripDelimiter) {
|
||||
joined.writePosition(joined.writePosition() - matcher.delimiter().length);
|
||||
joined.writePosition(joined.writePosition() - delimiterLength);
|
||||
}
|
||||
result.add(joined);
|
||||
chunks.clear();
|
||||
}
|
||||
buffer.readPosition(endIndex + 1);
|
||||
}
|
||||
while (buffer.readableByteCount() > 0);
|
||||
return (result != null ? result : Collections.emptyList());
|
||||
@@ -187,7 +186,7 @@ public final class StringDecoder extends AbstractDataBufferDecoder<String> {
|
||||
@Nullable MimeType mimeType, @Nullable Map<String, Object> hints) {
|
||||
|
||||
Charset charset = getCharset(mimeType);
|
||||
CharBuffer charBuffer = charset.decode(dataBuffer.asByteBuffer());
|
||||
CharBuffer charBuffer = charset.decode(dataBuffer.toByteBuffer());
|
||||
DataBufferUtils.release(dataBuffer);
|
||||
String value = charBuffer.toString();
|
||||
LogFormatUtils.traceDebug(logger, traceOn -> {
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright 2002-2022 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.core.io.buffer;
|
||||
|
||||
/**
|
||||
* Extension of {@link DataBuffer} that allows for buffers that can be used
|
||||
* in a {@code try}-with-resources statement.
|
||||
|
||||
* @author Arjen Poutsma
|
||||
* @since 6.0
|
||||
*/
|
||||
public interface CloseableDataBuffer extends DataBuffer, AutoCloseable {
|
||||
|
||||
/**
|
||||
* Closes this data buffer, freeing any resources.
|
||||
* @throws IllegalStateException if this buffer has already been closed
|
||||
*/
|
||||
@Override
|
||||
void close();
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2022 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.
|
||||
@@ -111,7 +111,10 @@ public interface DataBuffer {
|
||||
* the current capacity, it will be expanded.
|
||||
* @param capacity the new capacity
|
||||
* @return this buffer
|
||||
* @deprecated as of 6.0, in favor of {@link #ensureWritable(int)}, which
|
||||
* has different semantics
|
||||
*/
|
||||
@Deprecated
|
||||
DataBuffer capacity(int capacity);
|
||||
|
||||
/**
|
||||
@@ -121,11 +124,23 @@ public interface DataBuffer {
|
||||
* @param capacity the writable capacity to check for
|
||||
* @return this buffer
|
||||
* @since 5.1.4
|
||||
* @deprecated since 6.0, in favor of {@link #ensureWritable(int)}
|
||||
*/
|
||||
@Deprecated
|
||||
default DataBuffer ensureCapacity(int capacity) {
|
||||
return this;
|
||||
return ensureWritable(capacity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that the current buffer has enough {@link #writableByteCount()}
|
||||
* to write the amount of data given as an argument. If not, the missing
|
||||
* capacity will be added to the buffer.
|
||||
* @param capacity the writable capacity to check for
|
||||
* @return this buffer
|
||||
* @since 6.0
|
||||
*/
|
||||
DataBuffer ensureWritable(int capacity);
|
||||
|
||||
/**
|
||||
* Return the position from which this buffer will read.
|
||||
* @return the read position
|
||||
@@ -286,7 +301,10 @@ public interface DataBuffer {
|
||||
* @param index the index at which to start the slice
|
||||
* @param length the length of the slice
|
||||
* @return the specified slice of this data buffer
|
||||
* @deprecated as of 6.0, in favor of {@link #split(int)}, which
|
||||
* has different semantics
|
||||
*/
|
||||
@Deprecated
|
||||
DataBuffer slice(int index, int length);
|
||||
|
||||
/**
|
||||
@@ -301,18 +319,44 @@ public interface DataBuffer {
|
||||
* @param length the length of the slice
|
||||
* @return the specified, retained slice of this data buffer
|
||||
* @since 5.2
|
||||
* @deprecated as of 6.0, in favor of {@link #split(int)}, which
|
||||
* has different semantics
|
||||
*/
|
||||
@Deprecated
|
||||
default DataBuffer retainedSlice(int index, int length) {
|
||||
return DataBufferUtils.retain(slice(index, length));
|
||||
}
|
||||
|
||||
/**
|
||||
* Splits this data buffer into two at the given index.
|
||||
*
|
||||
* <p>Data that precedes the {@code index} will be returned in a new buffer,
|
||||
* while this buffer will contain data that follows after {@code index}.
|
||||
* Memory between the two buffers is shared, but independent and cannot
|
||||
* overlap (unlike {@link #slice(int, int) slice}).
|
||||
*
|
||||
* <p>The {@linkplain #readPosition() read} and
|
||||
* {@linkplain #writePosition() write} position of the returned buffer are
|
||||
* truncated to fit within the buffers {@linkplain #capacity() capacity} if
|
||||
* necessary. The positions of this buffer are set to {@code 0} if they are
|
||||
* smaller than {@code index}.
|
||||
* @param index the index at which it should be split
|
||||
* @return a new data buffer, containing the bytes from index {@code 0} to
|
||||
* {@code index}
|
||||
* @since 6.0
|
||||
*/
|
||||
DataBuffer split(int index);
|
||||
|
||||
/**
|
||||
* Expose this buffer's bytes as a {@link ByteBuffer}. Data between this
|
||||
* {@code DataBuffer} and the returned {@code ByteBuffer} is shared; though
|
||||
* 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
|
||||
ByteBuffer asByteBuffer();
|
||||
|
||||
/**
|
||||
@@ -324,9 +368,32 @@ 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
|
||||
ByteBuffer asByteBuffer(int index, int length);
|
||||
|
||||
/**
|
||||
* Returns a {@link ByteBuffer} representation of this data buffer. Data
|
||||
* between this {@code DataBuffer} and the returned {@code ByteBuffer} is
|
||||
* <strong>not</strong> shared.
|
||||
* @return this data buffer as a byte buffer
|
||||
* @since 6.0
|
||||
*/
|
||||
default ByteBuffer toByteBuffer() {
|
||||
return toByteBuffer(readPosition(), readableByteCount());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link ByteBuffer} representation of a subsequence of this
|
||||
* buffer's bytes. Data between this {@code DataBuffer} and the returned
|
||||
* {@code ByteBuffer} is <strong>not</strong> shared.
|
||||
* @return this data buffer as a byte buffer
|
||||
* @since 6.0
|
||||
*/
|
||||
ByteBuffer toByteBuffer(int index, int length);
|
||||
|
||||
/**
|
||||
* 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
|
||||
@@ -335,7 +402,9 @@ public interface DataBuffer {
|
||||
* @return this data buffer as an input stream
|
||||
* @see #asInputStream(boolean)
|
||||
*/
|
||||
InputStream asInputStream();
|
||||
default InputStream asInputStream() {
|
||||
return new DataBufferInputStream(this, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Expose this buffer's data as an {@link InputStream}. Both data and read position are
|
||||
@@ -346,14 +415,18 @@ public interface DataBuffer {
|
||||
* @return this data buffer as an input stream
|
||||
* @since 5.0.4
|
||||
*/
|
||||
InputStream asInputStream(boolean releaseOnClose);
|
||||
default InputStream asInputStream(boolean releaseOnClose) {
|
||||
return new DataBufferInputStream(this, releaseOnClose);
|
||||
};
|
||||
|
||||
/**
|
||||
* Expose this buffer's data as an {@link OutputStream}. Both data and write position are
|
||||
* shared between the returned stream and this data buffer.
|
||||
* @return this data buffer as an output stream
|
||||
*/
|
||||
OutputStream asOutputStream();
|
||||
default OutputStream asOutputStream() {
|
||||
return new DataBufferOutputStream(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return this buffer's data a String using the specified charset. Default implementation
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* Copyright 2002-2022 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.
|
||||
@@ -34,7 +34,9 @@ public interface DataBufferFactory {
|
||||
* underlying implementation and its configuration, this will be heap-based
|
||||
* or direct buffer.
|
||||
* @return the allocated buffer
|
||||
* @deprecated as of 6.0, in favor of {@link #allocateBuffer(int)}
|
||||
*/
|
||||
@Deprecated
|
||||
DataBuffer allocateBuffer();
|
||||
|
||||
/**
|
||||
@@ -75,4 +77,14 @@ public interface DataBufferFactory {
|
||||
*/
|
||||
DataBuffer join(List<? extends DataBuffer> dataBuffers);
|
||||
|
||||
/**
|
||||
* Indicates whether this factory allocates direct buffers (i.e. non-heap,
|
||||
* native memory).
|
||||
* @return {@code true} if this factory allocates direct buffers;
|
||||
* {@code false} otherwise
|
||||
* @since 6.0
|
||||
*/
|
||||
boolean isDirect();
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* Copyright 2002-2022 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.core.io.buffer;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* An {@link InputStream} that reads from a {@link DataBuffer}.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @since 6.0
|
||||
* @see DataBuffer#asInputStream(boolean)
|
||||
*/
|
||||
final class DataBufferInputStream extends InputStream {
|
||||
|
||||
private final DataBuffer dataBuffer;
|
||||
|
||||
private final int end;
|
||||
|
||||
private final boolean releaseOnClose;
|
||||
|
||||
private boolean closed;
|
||||
|
||||
private int mark;
|
||||
|
||||
|
||||
public DataBufferInputStream(DataBuffer dataBuffer, boolean releaseOnClose) {
|
||||
Assert.notNull(dataBuffer, "DataBuffer must not be null");
|
||||
this.dataBuffer = dataBuffer;
|
||||
int start = this.dataBuffer.readPosition();
|
||||
this.end = start + this.dataBuffer.readableByteCount();
|
||||
this.mark = start;
|
||||
this.releaseOnClose = releaseOnClose;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
checkClosed();
|
||||
if (available() == 0) {
|
||||
return -1;
|
||||
}
|
||||
return this.dataBuffer.read() & 0xFF;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(byte[] b, int off, int len) throws IOException {
|
||||
checkClosed();
|
||||
int available = available();
|
||||
if (available == 0) {
|
||||
return -1;
|
||||
}
|
||||
len = Math.min(available, len);
|
||||
this.dataBuffer.read(b, off, len);
|
||||
return len;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean markSupported() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mark(int mark) {
|
||||
this.mark = mark;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int available() {
|
||||
return Math.max(0, this.end - this.dataBuffer.readPosition());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
this.dataBuffer.readPosition(this.mark);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if (this.closed) {
|
||||
return;
|
||||
}
|
||||
if (this.releaseOnClose) {
|
||||
DataBufferUtils.release(this.dataBuffer);
|
||||
}
|
||||
this.closed = true;
|
||||
}
|
||||
|
||||
private void checkClosed() throws IOException {
|
||||
if (this.closed) {
|
||||
throw new IOException("DataBufferInputStream is closed");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright 2002-2022 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.core.io.buffer;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* An {@link OutputStream} that writes to a {@link DataBuffer}.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @since 6.0
|
||||
* @see DataBuffer#asOutputStream()
|
||||
*/
|
||||
final class DataBufferOutputStream extends OutputStream {
|
||||
|
||||
private final DataBuffer dataBuffer;
|
||||
|
||||
private boolean closed;
|
||||
|
||||
|
||||
public DataBufferOutputStream(DataBuffer dataBuffer) {
|
||||
Assert.notNull(dataBuffer, "DataBuffer must not be null");
|
||||
this.dataBuffer = dataBuffer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(int b) throws IOException {
|
||||
checkClosed();
|
||||
this.dataBuffer.ensureWritable(1);
|
||||
this.dataBuffer.write((byte) b);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(byte[] b, int off, int len) throws IOException {
|
||||
checkClosed();
|
||||
if (len > 0) {
|
||||
this.dataBuffer.ensureWritable(len);
|
||||
this.dataBuffer.write(b, off, len);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if (this.closed) {
|
||||
return;
|
||||
}
|
||||
this.closed = true;
|
||||
}
|
||||
|
||||
private void checkClosed() throws IOException {
|
||||
if (this.closed) {
|
||||
throw new IOException("DataBufferOutputStream is closed");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -156,7 +156,7 @@ public abstract class DataBufferUtils {
|
||||
// and then complete after releasing the DataBuffer.
|
||||
});
|
||||
|
||||
return flux.doOnDiscard(PooledDataBuffer.class, DataBufferUtils::release);
|
||||
return flux.doOnDiscard(DataBuffer.class, DataBufferUtils::release);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -417,7 +417,8 @@ public abstract class DataBufferUtils {
|
||||
* @param maxByteCount the maximum byte count
|
||||
* @return a flux whose maximum byte count is {@code maxByteCount}
|
||||
*/
|
||||
public static Flux<DataBuffer> takeUntilByteCount(Publisher<? extends DataBuffer> publisher, long maxByteCount) {
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T extends DataBuffer> Flux<T> takeUntilByteCount(Publisher<T> publisher, long maxByteCount) {
|
||||
Assert.notNull(publisher, "Publisher must not be null");
|
||||
Assert.isTrue(maxByteCount >= 0, "'maxByteCount' must be a positive number");
|
||||
|
||||
@@ -427,8 +428,10 @@ public abstract class DataBufferUtils {
|
||||
.map(buffer -> {
|
||||
long remainder = countDown.addAndGet(-buffer.readableByteCount());
|
||||
if (remainder < 0) {
|
||||
int length = buffer.readableByteCount() + (int) remainder;
|
||||
return buffer.slice(0, length);
|
||||
int index = buffer.readableByteCount() + (int) remainder;
|
||||
DataBuffer split = buffer.split(index);
|
||||
release(buffer);
|
||||
return (T)split;
|
||||
}
|
||||
else {
|
||||
return buffer;
|
||||
@@ -448,7 +451,7 @@ public abstract class DataBufferUtils {
|
||||
* @param maxByteCount the maximum byte count
|
||||
* @return a flux with the remaining part of the given publisher
|
||||
*/
|
||||
public static Flux<DataBuffer> skipUntilByteCount(Publisher<? extends DataBuffer> publisher, long maxByteCount) {
|
||||
public static <T extends DataBuffer> Flux<T> skipUntilByteCount(Publisher<T> publisher, long maxByteCount) {
|
||||
Assert.notNull(publisher, "Publisher must not be null");
|
||||
Assert.isTrue(maxByteCount >= 0, "'maxByteCount' must be a positive number");
|
||||
|
||||
@@ -464,14 +467,15 @@ public abstract class DataBufferUtils {
|
||||
if (remainder < 0) {
|
||||
countDown.set(0);
|
||||
int start = buffer.readableByteCount() + (int)remainder;
|
||||
int length = (int) -remainder;
|
||||
return buffer.slice(start, length);
|
||||
DataBuffer split = buffer.split(start);
|
||||
release(split);
|
||||
return buffer;
|
||||
}
|
||||
else {
|
||||
return buffer;
|
||||
}
|
||||
});
|
||||
}).doOnDiscard(PooledDataBuffer.class, DataBufferUtils::release);
|
||||
}).doOnDiscard(DataBuffer.class, DataBufferUtils::release);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -499,8 +503,8 @@ public abstract class DataBufferUtils {
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T extends DataBuffer> T touch(T dataBuffer, Object hint) {
|
||||
if (dataBuffer instanceof PooledDataBuffer pooledDataBuffer) {
|
||||
return (T) pooledDataBuffer.touch(hint);
|
||||
if (dataBuffer instanceof TouchableDataBuffer touchableDataBuffer) {
|
||||
return (T) touchableDataBuffer.touch(hint);
|
||||
}
|
||||
else {
|
||||
return dataBuffer;
|
||||
@@ -508,8 +512,11 @@ public abstract class DataBufferUtils {
|
||||
}
|
||||
|
||||
/**
|
||||
* Release the given data buffer, if it is a {@link PooledDataBuffer} and
|
||||
* has been {@linkplain PooledDataBuffer#isAllocated() allocated}.
|
||||
* Release the given data buffer. If it is a {@link PooledDataBuffer} and
|
||||
* has been {@linkplain PooledDataBuffer#isAllocated() allocated}, this
|
||||
* method will call {@link PooledDataBuffer#release()}. If it is a
|
||||
* {@link CloseableDataBuffer}, this method will call
|
||||
* {@link CloseableDataBuffer#close()}.
|
||||
* @param dataBuffer the data buffer to release
|
||||
* @return {@code true} if the buffer was released; {@code false} otherwise.
|
||||
*/
|
||||
@@ -520,7 +527,6 @@ public abstract class DataBufferUtils {
|
||||
return pooledDataBuffer.release();
|
||||
}
|
||||
catch (IllegalStateException ex) {
|
||||
// Avoid dependency on Netty: IllegalReferenceCountException
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Failed to release PooledDataBuffer: " + dataBuffer, ex);
|
||||
}
|
||||
@@ -528,6 +534,19 @@ public abstract class DataBufferUtils {
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (dataBuffer instanceof CloseableDataBuffer closeableDataBuffer) {
|
||||
try {
|
||||
closeableDataBuffer.close();
|
||||
return true;
|
||||
}
|
||||
catch (IllegalStateException ex) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Failed to release CloseableDataBuffer " + dataBuffer, ex);
|
||||
}
|
||||
return false;
|
||||
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -581,7 +600,7 @@ public abstract class DataBufferUtils {
|
||||
.collect(() -> new LimitedDataBufferList(maxByteCount), LimitedDataBufferList::add)
|
||||
.filter(list -> !list.isEmpty())
|
||||
.map(list -> list.get(0).factory().join(list))
|
||||
.doOnDiscard(PooledDataBuffer.class, DataBufferUtils::release);
|
||||
.doOnDiscard(DataBuffer.class, DataBufferUtils::release);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -887,14 +906,13 @@ public abstract class DataBufferUtils {
|
||||
|
||||
@Override
|
||||
public void accept(SynchronousSink<DataBuffer> sink) {
|
||||
boolean release = true;
|
||||
DataBuffer dataBuffer = this.dataBufferFactory.allocateBuffer(this.bufferSize);
|
||||
ByteBuffer byteBuffer = this.dataBufferFactory.isDirect() ?
|
||||
ByteBuffer.allocateDirect(this.bufferSize) :
|
||||
ByteBuffer.allocate(this.bufferSize);
|
||||
try {
|
||||
int read;
|
||||
ByteBuffer byteBuffer = dataBuffer.asByteBuffer(0, dataBuffer.capacity());
|
||||
if ((read = this.channel.read(byteBuffer)) >= 0) {
|
||||
dataBuffer.writePosition(read);
|
||||
release = false;
|
||||
if (this.channel.read(byteBuffer) >= 0) {
|
||||
byteBuffer.flip();
|
||||
DataBuffer dataBuffer = this.dataBufferFactory.wrap(byteBuffer);
|
||||
sink.next(dataBuffer);
|
||||
}
|
||||
else {
|
||||
@@ -904,16 +922,11 @@ public abstract class DataBufferUtils {
|
||||
catch (IOException ex) {
|
||||
sink.error(ex);
|
||||
}
|
||||
finally {
|
||||
if (release) {
|
||||
release(dataBuffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class ReadCompletionHandler implements CompletionHandler<Integer, DataBuffer> {
|
||||
private static class ReadCompletionHandler implements CompletionHandler<Integer, ByteBuffer> {
|
||||
|
||||
private final AsynchronousFileChannel channel;
|
||||
|
||||
@@ -965,21 +978,20 @@ public abstract class DataBufferUtils {
|
||||
}
|
||||
|
||||
private void read() {
|
||||
DataBuffer dataBuffer = this.dataBufferFactory.allocateBuffer(this.bufferSize);
|
||||
ByteBuffer byteBuffer = dataBuffer.asByteBuffer(0, this.bufferSize);
|
||||
this.channel.read(byteBuffer, this.position.get(), dataBuffer, this);
|
||||
ByteBuffer byteBuffer = this.dataBufferFactory.isDirect() ?
|
||||
ByteBuffer.allocateDirect(this.bufferSize) :
|
||||
ByteBuffer.allocate(this.bufferSize);
|
||||
this.channel.read(byteBuffer, this.position.get(), byteBuffer, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void completed(Integer read, DataBuffer dataBuffer) {
|
||||
public void completed(Integer read, ByteBuffer byteBuffer) {
|
||||
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();
|
||||
@@ -987,7 +999,9 @@ public abstract class DataBufferUtils {
|
||||
}
|
||||
|
||||
this.position.addAndGet(read);
|
||||
dataBuffer.writePosition(read);
|
||||
|
||||
byteBuffer.flip();
|
||||
DataBuffer dataBuffer = this.dataBufferFactory.wrap(byteBuffer);
|
||||
this.sink.next(dataBuffer);
|
||||
|
||||
// Stay in READING mode if there is demand
|
||||
@@ -1003,8 +1017,7 @@ public abstract class DataBufferUtils {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failed(Throwable exc, DataBuffer dataBuffer) {
|
||||
release(dataBuffer);
|
||||
public void failed(Throwable exc, ByteBuffer byteBuffer) {
|
||||
closeChannel(this.channel);
|
||||
this.state.set(State.DISPOSED);
|
||||
this.sink.error(exc);
|
||||
@@ -1035,7 +1048,7 @@ public abstract class DataBufferUtils {
|
||||
@Override
|
||||
protected void hookOnNext(DataBuffer dataBuffer) {
|
||||
try {
|
||||
ByteBuffer byteBuffer = dataBuffer.asByteBuffer();
|
||||
ByteBuffer byteBuffer = dataBuffer.toByteBuffer();
|
||||
while (byteBuffer.hasRemaining()) {
|
||||
this.channel.write(byteBuffer);
|
||||
}
|
||||
@@ -1099,7 +1112,7 @@ public abstract class DataBufferUtils {
|
||||
if (!this.dataBuffer.compareAndSet(null, value)) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
ByteBuffer byteBuffer = value.asByteBuffer();
|
||||
ByteBuffer byteBuffer = value.toByteBuffer();
|
||||
this.channel.write(byteBuffer, this.position.get(), byteBuffer, this);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2022 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.
|
||||
@@ -85,15 +85,22 @@ public class DataBufferWrapper implements DataBuffer {
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public DataBuffer capacity(int capacity) {
|
||||
return this.delegate.capacity(capacity);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public DataBuffer ensureCapacity(int capacity) {
|
||||
return this.delegate.ensureCapacity(capacity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataBuffer ensureWritable(int capacity) {
|
||||
return this.delegate.ensureWritable(capacity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int readPosition() {
|
||||
return this.delegate.readPosition();
|
||||
@@ -166,25 +173,44 @@ public class DataBufferWrapper implements DataBuffer {
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public DataBuffer slice(int index, int length) {
|
||||
return this.delegate.slice(index, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public DataBuffer retainedSlice(int index, int length) {
|
||||
return this.delegate.retainedSlice(index, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataBuffer split(int index) {
|
||||
return this.delegate.split(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public ByteBuffer asByteBuffer() {
|
||||
return this.delegate.asByteBuffer();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public ByteBuffer asByteBuffer(int index, int length) {
|
||||
return this.delegate.asByteBuffer(index, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer toByteBuffer() {
|
||||
return this.delegate.toByteBuffer();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer toByteBuffer(int index, int length) {
|
||||
return this.delegate.toByteBuffer(index, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream asInputStream() {
|
||||
return this.delegate.asInputStream();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* Copyright 2002-2022 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,9 +16,6 @@
|
||||
|
||||
package org.springframework.core.io.buffer;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Arrays;
|
||||
@@ -179,9 +176,15 @@ public class DefaultDataBuffer implements DataBuffer {
|
||||
}
|
||||
|
||||
@Override
|
||||
public DefaultDataBuffer capacity(int newCapacity) {
|
||||
if (newCapacity <= 0) {
|
||||
throw new IllegalArgumentException(String.format("'newCapacity' %d must be higher than 0", newCapacity));
|
||||
@Deprecated
|
||||
public DataBuffer capacity(int capacity) {
|
||||
setCapacity(capacity);
|
||||
return this;
|
||||
}
|
||||
|
||||
private void setCapacity(int newCapacity) {
|
||||
if (newCapacity < 0) {
|
||||
throw new IllegalArgumentException(String.format("'newCapacity' %d must be 0 or higher", newCapacity));
|
||||
}
|
||||
int readPosition = readPosition();
|
||||
int writePosition = writePosition();
|
||||
@@ -215,14 +218,13 @@ public class DefaultDataBuffer implements DataBuffer {
|
||||
}
|
||||
setNativeBuffer(newBuffer);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataBuffer ensureCapacity(int length) {
|
||||
public DataBuffer ensureWritable(int length) {
|
||||
if (length > writableByteCount()) {
|
||||
int newCapacity = calculateCapacity(this.writePosition + length);
|
||||
capacity(newCapacity);
|
||||
setCapacity(newCapacity);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
@@ -273,7 +275,7 @@ public class DefaultDataBuffer implements DataBuffer {
|
||||
|
||||
@Override
|
||||
public DefaultDataBuffer write(byte b) {
|
||||
ensureCapacity(1);
|
||||
ensureWritable(1);
|
||||
int pos = this.writePosition;
|
||||
this.byteBuffer.put(pos, b);
|
||||
this.writePosition = pos + 1;
|
||||
@@ -290,7 +292,7 @@ public class DefaultDataBuffer implements DataBuffer {
|
||||
@Override
|
||||
public DefaultDataBuffer write(byte[] source, int offset, int length) {
|
||||
Assert.notNull(source, "Byte array must not be null");
|
||||
ensureCapacity(length);
|
||||
ensureWritable(length);
|
||||
|
||||
ByteBuffer tmp = this.byteBuffer.duplicate();
|
||||
int limit = this.writePosition + length;
|
||||
@@ -304,7 +306,7 @@ public class DefaultDataBuffer implements DataBuffer {
|
||||
@Override
|
||||
public DefaultDataBuffer write(DataBuffer... buffers) {
|
||||
if (!ObjectUtils.isEmpty(buffers)) {
|
||||
write(Arrays.stream(buffers).map(DataBuffer::asByteBuffer).toArray(ByteBuffer[]::new));
|
||||
write(Arrays.stream(buffers).map(DataBuffer::toByteBuffer).toArray(ByteBuffer[]::new));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
@@ -313,7 +315,7 @@ public class DefaultDataBuffer implements DataBuffer {
|
||||
public DefaultDataBuffer write(ByteBuffer... buffers) {
|
||||
if (!ObjectUtils.isEmpty(buffers)) {
|
||||
int capacity = Arrays.stream(buffers).mapToInt(ByteBuffer::remaining).sum();
|
||||
ensureCapacity(capacity);
|
||||
ensureWritable(capacity);
|
||||
Arrays.stream(buffers).forEach(this::write);
|
||||
}
|
||||
return this;
|
||||
@@ -329,6 +331,7 @@ public class DefaultDataBuffer implements DataBuffer {
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public DefaultDataBuffer slice(int index, int length) {
|
||||
checkIndex(index, length);
|
||||
int oldPosition = this.byteBuffer.position();
|
||||
@@ -344,11 +347,37 @@ public class DefaultDataBuffer implements DataBuffer {
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataBuffer split(int index) {
|
||||
checkIndex(index);
|
||||
|
||||
ByteBuffer split = this.byteBuffer.duplicate().clear()
|
||||
.position(0)
|
||||
.limit(index)
|
||||
.slice();
|
||||
|
||||
DefaultDataBuffer result = new DefaultDataBuffer(this.dataBufferFactory, split);
|
||||
result.writePosition = Math.min(this.writePosition, index);
|
||||
result.readPosition = Math.min(this.readPosition, index);
|
||||
|
||||
this.byteBuffer = this.byteBuffer.duplicate().clear()
|
||||
.position(index)
|
||||
.limit(this.byteBuffer.capacity())
|
||||
.slice();
|
||||
this.writePosition = Math.max(this.writePosition, index) - index;
|
||||
this.readPosition = Math.max(this.readPosition, index) - index;
|
||||
capacity(this.byteBuffer.capacity());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public ByteBuffer asByteBuffer() {
|
||||
return asByteBuffer(this.readPosition, readableByteCount());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public ByteBuffer asByteBuffer(int index, int length) {
|
||||
checkIndex(index, length);
|
||||
|
||||
@@ -359,21 +388,16 @@ public class DefaultDataBuffer implements DataBuffer {
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream asInputStream() {
|
||||
return new DefaultDataBufferInputStream();
|
||||
}
|
||||
public ByteBuffer toByteBuffer(int index, int length) {
|
||||
checkIndex(index, length);
|
||||
|
||||
@Override
|
||||
public InputStream asInputStream(boolean releaseOnClose) {
|
||||
return new DefaultDataBufferInputStream();
|
||||
ByteBuffer copy = allocate(length, this.byteBuffer.isDirect());
|
||||
ByteBuffer readOnly = this.byteBuffer.asReadOnlyBuffer();
|
||||
readOnly.clear().position(index).limit(index + length);
|
||||
copy.put(readOnly);
|
||||
return copy.flip();
|
||||
}
|
||||
|
||||
@Override
|
||||
public OutputStream asOutputStream() {
|
||||
return new DefaultDataBufferOutputStream();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString(int index, int length, Charset charset) {
|
||||
checkIndex(index, length);
|
||||
@@ -452,9 +476,17 @@ public class DefaultDataBuffer implements DataBuffer {
|
||||
|
||||
|
||||
private void checkIndex(int index, int length) {
|
||||
checkIndex(index);
|
||||
checkLength(length);
|
||||
}
|
||||
|
||||
private void checkIndex(int index) {
|
||||
assertIndex(index >= 0, "index %d must be >= 0", index);
|
||||
assertIndex(length >= 0, "length %d must be >= 0", length);
|
||||
assertIndex(index <= this.capacity, "index %d must be <= %d", index, this.capacity);
|
||||
}
|
||||
|
||||
private void checkLength(int length) {
|
||||
assertIndex(length >= 0, "length %d must be >= 0", length);
|
||||
assertIndex(length <= this.capacity, "length %d must be <= %d", length, this.capacity);
|
||||
}
|
||||
|
||||
@@ -466,47 +498,6 @@ public class DefaultDataBuffer implements DataBuffer {
|
||||
}
|
||||
|
||||
|
||||
private class DefaultDataBufferInputStream extends InputStream {
|
||||
|
||||
@Override
|
||||
public int available() {
|
||||
return readableByteCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() {
|
||||
return available() > 0 ? DefaultDataBuffer.this.read() & 0xFF : -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(byte[] bytes, int off, int len) throws IOException {
|
||||
int available = available();
|
||||
if (available > 0) {
|
||||
len = Math.min(len, available);
|
||||
DefaultDataBuffer.this.read(bytes, off, len);
|
||||
return len;
|
||||
}
|
||||
else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private class DefaultDataBufferOutputStream extends OutputStream {
|
||||
|
||||
@Override
|
||||
public void write(int b) throws IOException {
|
||||
DefaultDataBuffer.this.write((byte) b);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(byte[] bytes, int off, int len) throws IOException {
|
||||
DefaultDataBuffer.this.write(bytes, off, len);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class SlicedDefaultDataBuffer extends DefaultDataBuffer {
|
||||
|
||||
SlicedDefaultDataBuffer(ByteBuffer byteBuffer, DefaultDataBufferFactory dataBufferFactory, int length) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2022 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.
|
||||
@@ -85,6 +85,7 @@ public class DefaultDataBufferFactory implements DataBufferFactory {
|
||||
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public DefaultDataBuffer allocateBuffer() {
|
||||
return allocateBuffer(this.defaultInitialCapacity);
|
||||
}
|
||||
@@ -122,6 +123,10 @@ public class DefaultDataBufferFactory implements DataBufferFactory {
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDirect() {
|
||||
return this.preferDirect;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
|
||||
@@ -0,0 +1,346 @@
|
||||
/*
|
||||
* Copyright 2002-2022 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.core.io.buffer;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.function.IntPredicate;
|
||||
|
||||
import io.netty5.buffer.api.Buffer;
|
||||
import io.netty5.util.AsciiString;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
|
||||
/**
|
||||
* Implementation of the {@code DataBuffer} interface that wraps a Netty 5
|
||||
* {@link Buffer}. Typically constructed with {@link Netty5DataBufferFactory}.
|
||||
*
|
||||
* @author Violeta Georgieva
|
||||
* @author Arjen Poutsma
|
||||
* @since 6.0
|
||||
*/
|
||||
public final class Netty5DataBuffer implements CloseableDataBuffer,
|
||||
TouchableDataBuffer {
|
||||
|
||||
private final Buffer buffer;
|
||||
|
||||
private final Netty5DataBufferFactory dataBufferFactory;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new {@code Netty5DataBuffer} based on the given {@code Buffer}.
|
||||
* @param buffer the buffer to base this buffer on
|
||||
*/
|
||||
Netty5DataBuffer(Buffer buffer, Netty5DataBufferFactory dataBufferFactory) {
|
||||
Assert.notNull(buffer, "Buffer must not be null");
|
||||
Assert.notNull(dataBufferFactory, "NettyDataBufferFactory must not be null");
|
||||
this.buffer = buffer;
|
||||
this.dataBufferFactory = dataBufferFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Directly exposes the native {@code Buffer} that this buffer is based on.
|
||||
* @return the wrapped buffer
|
||||
*/
|
||||
public Buffer getNativeBuffer() {
|
||||
return this.buffer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataBufferFactory factory() {
|
||||
return this.dataBufferFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int indexOf(IntPredicate predicate, int fromIndex) {
|
||||
Assert.notNull(predicate, "IntPredicate must not be null");
|
||||
if (fromIndex < 0) {
|
||||
fromIndex = 0;
|
||||
}
|
||||
else if (fromIndex >= this.buffer.writerOffset()) {
|
||||
return -1;
|
||||
}
|
||||
int length = this.buffer.writerOffset() - fromIndex;
|
||||
int bytes = this.buffer.openCursor(fromIndex, length).process(predicate.negate()::test);
|
||||
return bytes == -1 ? -1 : fromIndex + bytes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int lastIndexOf(IntPredicate predicate, int fromIndex) {
|
||||
Assert.notNull(predicate, "IntPredicate must not be null");
|
||||
if (fromIndex < 0) {
|
||||
return -1;
|
||||
}
|
||||
fromIndex = Math.min(fromIndex, this.buffer.writerOffset() - 1);
|
||||
return this.buffer.openCursor(0, fromIndex + 1).process(predicate.negate()::test);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int readableByteCount() {
|
||||
return this.buffer.readableBytes();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int writableByteCount() {
|
||||
return this.buffer.writableBytes();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int readPosition() {
|
||||
return this.buffer.readerOffset();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Netty5DataBuffer readPosition(int readPosition) {
|
||||
this.buffer.readerOffset(readPosition);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int writePosition() {
|
||||
return this.buffer.writerOffset();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Netty5DataBuffer writePosition(int writePosition) {
|
||||
this.buffer.writerOffset(writePosition);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte getByte(int index) {
|
||||
return this.buffer.getByte(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int capacity() {
|
||||
return this.buffer.capacity();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public Netty5DataBuffer capacity(int capacity) {
|
||||
if (capacity <= 0) {
|
||||
throw new IllegalArgumentException(String.format("'newCapacity' %d must be higher than 0", capacity));
|
||||
}
|
||||
int diff = capacity - capacity();
|
||||
if (diff > 0) {
|
||||
this.buffer.ensureWritable(this.buffer.writableBytes() + diff);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataBuffer ensureWritable(int capacity) {
|
||||
Assert.isTrue(capacity >= 0, "Capacity must be larger than 0");
|
||||
this.buffer.ensureWritable(capacity);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte read() {
|
||||
return this.buffer.readByte();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Netty5DataBuffer read(byte[] destination) {
|
||||
return read(destination, 0, destination.length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Netty5DataBuffer read(byte[] destination, int offset, int length) {
|
||||
this.buffer.readBytes(destination, offset, length);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Netty5DataBuffer write(byte b) {
|
||||
this.buffer.writeByte(b);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Netty5DataBuffer write(byte[] source) {
|
||||
this.buffer.writeBytes(source);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Netty5DataBuffer write(byte[] source, int offset, int length) {
|
||||
this.buffer.writeBytes(source, offset, length);
|
||||
return this;
|
||||
}
|
||||
|
||||
@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();
|
||||
}
|
||||
return write(nativeBuffers);
|
||||
}
|
||||
else {
|
||||
ByteBuffer[] byteBuffers = new ByteBuffer[buffers.length];
|
||||
for (int i = 0; i < buffers.length; i++) {
|
||||
byteBuffers[i] = buffers[i].toByteBuffer();
|
||||
|
||||
}
|
||||
return write(byteBuffers);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
private static boolean hasNetty5DataBuffers(DataBuffer[] buffers) {
|
||||
for (DataBuffer buffer : buffers) {
|
||||
if (!(buffer instanceof Netty5DataBuffer)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Netty5DataBuffer write(ByteBuffer... buffers) {
|
||||
if (!ObjectUtils.isEmpty(buffers)) {
|
||||
for (ByteBuffer buffer : buffers) {
|
||||
this.buffer.writeBytes(buffer);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes one or more Netty 5 {@link Buffer Buffers} to this buffer,
|
||||
* starting at the current writing position.
|
||||
* @param buffers the buffers to write into this buffer
|
||||
* @return this buffer
|
||||
*/
|
||||
public Netty5DataBuffer write(Buffer... buffers) {
|
||||
if (!ObjectUtils.isEmpty(buffers)) {
|
||||
for (Buffer buffer : buffers) {
|
||||
this.buffer.writeBytes(buffer);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataBuffer write(CharSequence charSequence, Charset charset) {
|
||||
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);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* <p><strong>Note</strong> that due to the lack of a {@code slice} method
|
||||
* in Netty 5's {@link Buffer}, this implementation returns a copy that
|
||||
* does <strong>not</strong> share its contents with this buffer.
|
||||
*/
|
||||
@Override
|
||||
@Deprecated
|
||||
public DataBuffer slice(int index, int length) {
|
||||
Buffer copy = this.buffer.copy(index, length);
|
||||
return new Netty5DataBuffer(copy, this.dataBufferFactory);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataBuffer split(int index) {
|
||||
Buffer split = this.buffer.split(index);
|
||||
return new Netty5DataBuffer(split, this.dataBufferFactory);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public ByteBuffer asByteBuffer() {
|
||||
return toByteBuffer();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public ByteBuffer asByteBuffer(int index, int length) {
|
||||
return toByteBuffer(index, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public ByteBuffer toByteBuffer(int index, int length) {
|
||||
ByteBuffer copy = this.buffer.isDirect() ?
|
||||
ByteBuffer.allocateDirect(length) :
|
||||
ByteBuffer.allocate(length);
|
||||
|
||||
this.buffer.copyInto(index, copy, 0, length);
|
||||
return copy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(Charset charset) {
|
||||
Assert.notNull(charset, "Charset must not be null");
|
||||
return this.buffer.toString(charset);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(int index, int length, Charset charset) {
|
||||
Assert.notNull(charset, "Charset must not be null");
|
||||
byte[] data = new byte[length];
|
||||
this.buffer.copyInto(index, data, 0, length);
|
||||
return new String(data, 0, length, charset);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Netty5DataBuffer touch(Object hint) {
|
||||
this.buffer.touch(hint);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
this.buffer.close();
|
||||
}
|
||||
|
||||
|
||||
public boolean equals(@Nullable Object other) {
|
||||
return (this == other || (other instanceof Netty5DataBuffer dataBuffer &&
|
||||
this.buffer.equals(dataBuffer.buffer)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return this.buffer.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.buffer.toString();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
/*
|
||||
* Copyright 2002-2022 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.core.io.buffer;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.List;
|
||||
|
||||
import io.netty5.buffer.api.Buffer;
|
||||
import io.netty5.buffer.api.BufferAllocator;
|
||||
import io.netty5.buffer.api.CompositeBuffer;
|
||||
import io.netty5.buffer.api.DefaultBufferAllocators;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Implementation of the {@code DataBufferFactory} interface based on a
|
||||
* Netty 5 {@link BufferAllocator}.
|
||||
*
|
||||
* @author Violeta Georgieva
|
||||
* @author Arjen Poutsma
|
||||
* @since 6.0
|
||||
*/
|
||||
public class Netty5DataBufferFactory implements DataBufferFactory {
|
||||
|
||||
private final BufferAllocator bufferAllocator;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new {@code Netty5DataBufferFactory} based on the given factory.
|
||||
* @param bufferAllocator the factory to use
|
||||
*/
|
||||
public Netty5DataBufferFactory(BufferAllocator bufferAllocator) {
|
||||
Assert.notNull(bufferAllocator, "BufferAllocator must not be null");
|
||||
this.bufferAllocator = bufferAllocator;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the {@code BufferAllocator} used by this factory.
|
||||
*/
|
||||
public BufferAllocator getBufferAllocator() {
|
||||
return this.bufferAllocator;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public Netty5DataBuffer allocateBuffer() {
|
||||
Buffer buffer = this.bufferAllocator.allocate(256);
|
||||
return new Netty5DataBuffer(buffer, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Netty5DataBuffer allocateBuffer(int initialCapacity) {
|
||||
Buffer buffer = this.bufferAllocator.allocate(initialCapacity);
|
||||
return new Netty5DataBuffer(buffer, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Netty5DataBuffer wrap(ByteBuffer byteBuffer) {
|
||||
Buffer buffer = this.bufferAllocator.copyOf(byteBuffer);
|
||||
return new Netty5DataBuffer(buffer, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Netty5DataBuffer wrap(byte[] bytes) {
|
||||
Buffer buffer = this.bufferAllocator.copyOf(bytes);
|
||||
return new Netty5DataBuffer(buffer, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap the given Netty {@link Buffer} in a {@code Netty5DataBuffer}.
|
||||
* @param buffer the Netty buffer to wrap
|
||||
* @return the wrapped buffer
|
||||
*/
|
||||
public Netty5DataBuffer wrap(Buffer buffer) {
|
||||
buffer.touch("Wrap buffer");
|
||||
return new Netty5DataBuffer(buffer, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* <p>This implementation uses Netty's {@link CompositeBuffer}.
|
||||
*/
|
||||
@Override
|
||||
public DataBuffer join(List<? extends DataBuffer> dataBuffers) {
|
||||
Assert.notEmpty(dataBuffers, "DataBuffer List must not be empty");
|
||||
if (dataBuffers.size() == 1) {
|
||||
return dataBuffers.get(0);
|
||||
}
|
||||
CompositeBuffer composite = this.bufferAllocator.compose();
|
||||
for (DataBuffer dataBuffer : dataBuffers) {
|
||||
Assert.isInstanceOf(Netty5DataBuffer.class, dataBuffer);
|
||||
composite.extendWith(((Netty5DataBuffer) dataBuffer).getNativeBuffer().send());
|
||||
}
|
||||
return new Netty5DataBuffer(composite, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDirect() {
|
||||
return this.bufferAllocator.getAllocationType().isDirect();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the given Netty {@link DataBuffer} as a {@link Buffer}.
|
||||
* <p>Returns the {@linkplain Netty5DataBuffer#getNativeBuffer() native buffer}
|
||||
* if {@code buffer} is a {@link Netty5DataBuffer}; returns
|
||||
* {@link BufferAllocator#copyOf(ByteBuffer)} otherwise.
|
||||
* @param buffer the {@code DataBuffer} to return a {@code Buffer} for
|
||||
* @return the netty {@code Buffer}
|
||||
*/
|
||||
public static Buffer toBuffer(DataBuffer buffer) {
|
||||
if (buffer instanceof Netty5DataBuffer netty5DataBuffer) {
|
||||
return netty5DataBuffer.getNativeBuffer();
|
||||
}
|
||||
else {
|
||||
return DefaultBufferAllocators.preferredAllocator().copyOf(buffer.toByteBuffer());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Netty5DataBufferFactory (" + this.bufferAllocator + ")";
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2022 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,16 +16,12 @@
|
||||
|
||||
package org.springframework.core.io.buffer;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.function.IntPredicate;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufInputStream;
|
||||
import io.netty.buffer.ByteBufOutputStream;
|
||||
import io.netty.buffer.ByteBufUtil;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
@@ -33,7 +29,7 @@ import org.springframework.util.Assert;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
/**
|
||||
* Implementation of the {@code DataBuffer} interface that wraps a Netty
|
||||
* Implementation of the {@code DataBuffer} interface that wraps a Netty 4
|
||||
* {@link ByteBuf}. Typically constructed with {@link NettyDataBufferFactory}.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
@@ -42,7 +38,7 @@ import org.springframework.util.ObjectUtils;
|
||||
*/
|
||||
public class NettyDataBuffer implements PooledDataBuffer {
|
||||
|
||||
private final ByteBuf byteBuf;
|
||||
private ByteBuf byteBuf;
|
||||
|
||||
private final NettyDataBufferFactory dataBufferFactory;
|
||||
|
||||
@@ -138,13 +134,14 @@ public class NettyDataBuffer implements PooledDataBuffer {
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public NettyDataBuffer capacity(int capacity) {
|
||||
this.byteBuf.capacity(capacity);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataBuffer ensureCapacity(int capacity) {
|
||||
public DataBuffer ensureWritable(int capacity) {
|
||||
this.byteBuf.ensureWritable(capacity);
|
||||
return this;
|
||||
}
|
||||
@@ -197,8 +194,7 @@ public class NettyDataBuffer implements PooledDataBuffer {
|
||||
else {
|
||||
ByteBuffer[] byteBuffers = new ByteBuffer[buffers.length];
|
||||
for (int i = 0; i < buffers.length; i++) {
|
||||
byteBuffers[i] = buffers[i].asByteBuffer();
|
||||
|
||||
byteBuffers[i] = buffers[i].toByteBuffer();
|
||||
}
|
||||
write(byteBuffers);
|
||||
}
|
||||
@@ -257,40 +253,56 @@ public class NettyDataBuffer implements PooledDataBuffer {
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public NettyDataBuffer slice(int index, int length) {
|
||||
ByteBuf slice = this.byteBuf.slice(index, length);
|
||||
return new NettyDataBuffer(slice, this.dataBufferFactory);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public NettyDataBuffer retainedSlice(int index, int length) {
|
||||
ByteBuf slice = this.byteBuf.retainedSlice(index, length);
|
||||
return new NettyDataBuffer(slice, this.dataBufferFactory);
|
||||
}
|
||||
|
||||
@Override
|
||||
public NettyDataBuffer split(int index) {
|
||||
ByteBuf split = this.byteBuf.retainedSlice(0, index);
|
||||
int writerIndex = this.byteBuf.writerIndex();
|
||||
int readerIndex = this.byteBuf.readerIndex();
|
||||
|
||||
split.writerIndex(Math.min(writerIndex, index));
|
||||
split.readerIndex(Math.min(readerIndex, index));
|
||||
|
||||
this.byteBuf = this.byteBuf.slice(index, this.byteBuf.capacity() - index);
|
||||
this.byteBuf.writerIndex(Math.max(writerIndex, index) - index);
|
||||
this.byteBuf.readerIndex(Math.max(readerIndex, index) - index);
|
||||
|
||||
return new NettyDataBuffer(split, this.dataBufferFactory);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public ByteBuffer asByteBuffer() {
|
||||
return this.byteBuf.nioBuffer();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public ByteBuffer asByteBuffer(int index, int length) {
|
||||
return this.byteBuf.nioBuffer(index, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream asInputStream() {
|
||||
return new ByteBufInputStream(this.byteBuf);
|
||||
}
|
||||
public ByteBuffer toByteBuffer(int index, int length) {
|
||||
ByteBuffer result = this.byteBuf.isDirect() ?
|
||||
ByteBuffer.allocateDirect(length) :
|
||||
ByteBuffer.allocate(length);
|
||||
|
||||
@Override
|
||||
public InputStream asInputStream(boolean releaseOnClose) {
|
||||
return new ByteBufInputStream(this.byteBuf, releaseOnClose);
|
||||
}
|
||||
this.byteBuf.getBytes(index, result);
|
||||
|
||||
@Override
|
||||
public OutputStream asOutputStream() {
|
||||
return new ByteBufOutputStream(this.byteBuf);
|
||||
return result.flip();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -28,7 +28,7 @@ import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Implementation of the {@code DataBufferFactory} interface based on a
|
||||
* Netty {@link ByteBufAllocator}.
|
||||
* Netty 4 {@link ByteBufAllocator}.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @author Juergen Hoeller
|
||||
@@ -61,6 +61,7 @@ public class NettyDataBufferFactory implements DataBufferFactory {
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public NettyDataBuffer allocateBuffer() {
|
||||
ByteBuf byteBuf = this.byteBufAllocator.buffer();
|
||||
return new NettyDataBuffer(byteBuf, this);
|
||||
@@ -113,6 +114,11 @@ public class NettyDataBufferFactory implements DataBufferFactory {
|
||||
return new NettyDataBuffer(composite, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDirect() {
|
||||
return this.byteBufAllocator.isDirectBufferPooled();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the given Netty {@link DataBuffer} as a {@link ByteBuf}.
|
||||
* <p>Returns the {@linkplain NettyDataBuffer#getNativeBuffer() native buffer}
|
||||
@@ -126,7 +132,7 @@ public class NettyDataBufferFactory implements DataBufferFactory {
|
||||
return nettyDataBuffer.getNativeBuffer();
|
||||
}
|
||||
else {
|
||||
return Unpooled.wrappedBuffer(buffer.asByteBuffer());
|
||||
return Unpooled.wrappedBuffer(buffer.toByteBuffer());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2022 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.
|
||||
@@ -17,13 +17,13 @@
|
||||
package org.springframework.core.io.buffer;
|
||||
|
||||
/**
|
||||
* Extension of {@link DataBuffer} that allows for buffer that share
|
||||
* Extension of {@link DataBuffer} that allows for buffers that share
|
||||
* a memory pool. Introduces methods for reference counting.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @since 5.0
|
||||
*/
|
||||
public interface PooledDataBuffer extends DataBuffer {
|
||||
public interface PooledDataBuffer extends TouchableDataBuffer {
|
||||
|
||||
/**
|
||||
* Return {@code true} if this buffer is allocated;
|
||||
@@ -43,6 +43,7 @@ public interface PooledDataBuffer extends DataBuffer {
|
||||
* @return this buffer
|
||||
* @since 5.3.2
|
||||
*/
|
||||
@Override
|
||||
PooledDataBuffer touch(Object hint);
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright 2002-2022 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.core.io.buffer;
|
||||
|
||||
/**
|
||||
* Extension of {@link DataBuffer} that allows for buffers that can be given
|
||||
* hints for debugging purposes.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @since 6.0
|
||||
*/
|
||||
public interface TouchableDataBuffer extends DataBuffer {
|
||||
|
||||
/**
|
||||
* Associate the given hint with the data buffer for debugging purposes.
|
||||
* @return this buffer
|
||||
*/
|
||||
TouchableDataBuffer touch(Object hint);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* Copyright 2002-2022 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.core.codec;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import io.netty5.buffer.api.Buffer;
|
||||
import io.netty5.buffer.api.DefaultBufferAllocators;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.io.buffer.DataBuffer;
|
||||
import org.springframework.core.testfixture.codec.AbstractDecoderTests;
|
||||
import org.springframework.util.MimeTypeUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author Arjen Poutsma
|
||||
*/
|
||||
class Netty5BufferDecoderTests extends AbstractDecoderTests<Netty5BufferDecoder> {
|
||||
|
||||
private final byte[] fooBytes = "foo".getBytes(StandardCharsets.UTF_8);
|
||||
|
||||
private final byte[] barBytes = "bar".getBytes(StandardCharsets.UTF_8);
|
||||
|
||||
|
||||
Netty5BufferDecoderTests() {
|
||||
super(new Netty5BufferDecoder());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Test
|
||||
public void canDecode() {
|
||||
assertThat(this.decoder.canDecode(ResolvableType.forClass(Buffer.class),
|
||||
MimeTypeUtils.TEXT_PLAIN)).isTrue();
|
||||
assertThat(this.decoder.canDecode(ResolvableType.forClass(Integer.class),
|
||||
MimeTypeUtils.TEXT_PLAIN)).isFalse();
|
||||
assertThat(this.decoder.canDecode(ResolvableType.forClass(Buffer.class),
|
||||
MimeTypeUtils.APPLICATION_JSON)).isTrue();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Test
|
||||
public void decode() {
|
||||
Flux<DataBuffer> input = Flux.concat(
|
||||
dataBuffer(this.fooBytes),
|
||||
dataBuffer(this.barBytes));
|
||||
|
||||
testDecodeAll(input, Buffer.class, step -> step
|
||||
.consumeNextWith(expectByteBuffer(DefaultBufferAllocators.preferredAllocator().copyOf(this.fooBytes)))
|
||||
.consumeNextWith(expectByteBuffer(DefaultBufferAllocators.preferredAllocator().copyOf(this.barBytes)))
|
||||
.verifyComplete());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Test
|
||||
public void decodeToMono() {
|
||||
Flux<DataBuffer> input = Flux.concat(
|
||||
dataBuffer(this.fooBytes),
|
||||
dataBuffer(this.barBytes));
|
||||
|
||||
Buffer expected = DefaultBufferAllocators.preferredAllocator().allocate(this.fooBytes.length + this.barBytes.length)
|
||||
.writeBytes(this.fooBytes)
|
||||
.writeBytes(this.barBytes)
|
||||
.readerOffset(0);
|
||||
|
||||
testDecodeToMonoAll(input, Buffer.class, step -> step
|
||||
.consumeNextWith(expectByteBuffer(expected))
|
||||
.verifyComplete());
|
||||
}
|
||||
|
||||
private Consumer<Buffer> expectByteBuffer(Buffer expected) {
|
||||
return actual -> assertThat(actual).isEqualTo(expected);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright 2002-2022 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.core.codec;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import io.netty5.buffer.api.Buffer;
|
||||
import io.netty5.buffer.api.DefaultBufferAllocators;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.testfixture.codec.AbstractEncoderTests;
|
||||
import org.springframework.util.MimeTypeUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author Arjen Poutsma
|
||||
*/
|
||||
class Netty5BufferEncoderTests extends AbstractEncoderTests<Netty5BufferEncoder> {
|
||||
|
||||
private final byte[] fooBytes = "foo".getBytes(StandardCharsets.UTF_8);
|
||||
|
||||
private final byte[] barBytes = "bar".getBytes(StandardCharsets.UTF_8);
|
||||
|
||||
Netty5BufferEncoderTests() {
|
||||
super(new Netty5BufferEncoder());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Test
|
||||
public void canEncode() {
|
||||
assertThat(this.encoder.canEncode(ResolvableType.forClass(Buffer.class),
|
||||
MimeTypeUtils.TEXT_PLAIN)).isTrue();
|
||||
assertThat(this.encoder.canEncode(ResolvableType.forClass(Integer.class),
|
||||
MimeTypeUtils.TEXT_PLAIN)).isFalse();
|
||||
assertThat(this.encoder.canEncode(ResolvableType.forClass(Buffer.class),
|
||||
MimeTypeUtils.APPLICATION_JSON)).isTrue();
|
||||
|
||||
// gh-20024
|
||||
assertThat(this.encoder.canEncode(ResolvableType.NONE, null)).isFalse();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Test
|
||||
public void encode() {
|
||||
Flux<Buffer> input = Flux.just(this.fooBytes, this.barBytes)
|
||||
.map(DefaultBufferAllocators.preferredAllocator()::copyOf);
|
||||
|
||||
testEncodeAll(input, Buffer.class, step -> step
|
||||
.consumeNextWith(expectBytes(this.fooBytes))
|
||||
.consumeNextWith(expectBytes(this.barBytes))
|
||||
.verifyComplete());
|
||||
}
|
||||
}
|
||||
@@ -28,6 +28,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatException;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
import static org.junit.jupiter.api.Assumptions.assumeFalse;
|
||||
|
||||
/**
|
||||
* @author Arjen Poutsma
|
||||
@@ -402,6 +403,9 @@ class DataBufferTests extends AbstractDataBufferAllocatingTests {
|
||||
|
||||
@ParameterizedDataBufferAllocatingTest
|
||||
void decreaseCapacityLowReadPosition(DataBufferFactory bufferFactory) {
|
||||
assumeFalse(bufferFactory instanceof Netty5DataBufferFactory,
|
||||
"Netty 5 does not support decreasing the capacity");
|
||||
|
||||
super.bufferFactory = bufferFactory;
|
||||
|
||||
DataBuffer buffer = createDataBuffer(2);
|
||||
@@ -414,6 +418,9 @@ class DataBufferTests extends AbstractDataBufferAllocatingTests {
|
||||
|
||||
@ParameterizedDataBufferAllocatingTest
|
||||
void decreaseCapacityHighReadPosition(DataBufferFactory bufferFactory) {
|
||||
assumeFalse(bufferFactory instanceof Netty5DataBufferFactory,
|
||||
"Netty 5 does not support decreasing the capacity");
|
||||
|
||||
super.bufferFactory = bufferFactory;
|
||||
|
||||
DataBuffer buffer = createDataBuffer(2);
|
||||
@@ -492,6 +499,7 @@ class DataBufferTests extends AbstractDataBufferAllocatingTests {
|
||||
}
|
||||
|
||||
@ParameterizedDataBufferAllocatingTest
|
||||
@SuppressWarnings("deprecation")
|
||||
void asByteBuffer(DataBufferFactory bufferFactory) {
|
||||
super.bufferFactory = bufferFactory;
|
||||
|
||||
@@ -513,6 +521,7 @@ class DataBufferTests extends AbstractDataBufferAllocatingTests {
|
||||
}
|
||||
|
||||
@ParameterizedDataBufferAllocatingTest
|
||||
@SuppressWarnings("deprecation")
|
||||
void asByteBufferIndexLength(DataBufferFactory bufferFactory) {
|
||||
super.bufferFactory = bufferFactory;
|
||||
|
||||
@@ -522,6 +531,9 @@ class DataBufferTests extends AbstractDataBufferAllocatingTests {
|
||||
ByteBuffer result = buffer.asByteBuffer(1, 2);
|
||||
assertThat(result.capacity()).isEqualTo(2);
|
||||
|
||||
assumeFalse(bufferFactory instanceof Netty5DataBufferFactory,
|
||||
"Netty 5 does share the internal buffer");
|
||||
|
||||
buffer.write((byte) 'c');
|
||||
assertThat(result.remaining()).isEqualTo(2);
|
||||
|
||||
@@ -533,7 +545,11 @@ class DataBufferTests extends AbstractDataBufferAllocatingTests {
|
||||
}
|
||||
|
||||
@ParameterizedDataBufferAllocatingTest
|
||||
@SuppressWarnings("deprecation")
|
||||
void byteBufferContainsDataBufferChanges(DataBufferFactory bufferFactory) {
|
||||
assumeFalse(bufferFactory instanceof Netty5DataBufferFactory,
|
||||
"Netty 5 does not support sharing data between buffers");
|
||||
|
||||
super.bufferFactory = bufferFactory;
|
||||
|
||||
DataBuffer dataBuffer = createDataBuffer(1);
|
||||
@@ -549,7 +565,11 @@ class DataBufferTests extends AbstractDataBufferAllocatingTests {
|
||||
}
|
||||
|
||||
@ParameterizedDataBufferAllocatingTest
|
||||
@SuppressWarnings("deprecation")
|
||||
void dataBufferContainsByteBufferChanges(DataBufferFactory bufferFactory) {
|
||||
assumeFalse(bufferFactory instanceof Netty5DataBufferFactory,
|
||||
"Netty 5 does not support sharing data between buffers");
|
||||
|
||||
super.bufferFactory = bufferFactory;
|
||||
|
||||
DataBuffer dataBuffer = createDataBuffer(1);
|
||||
@@ -565,6 +585,7 @@ class DataBufferTests extends AbstractDataBufferAllocatingTests {
|
||||
}
|
||||
|
||||
@ParameterizedDataBufferAllocatingTest
|
||||
@SuppressWarnings("deprecation")
|
||||
void emptyAsByteBuffer(DataBufferFactory bufferFactory) {
|
||||
super.bufferFactory = bufferFactory;
|
||||
|
||||
@@ -576,6 +597,45 @@ class DataBufferTests extends AbstractDataBufferAllocatingTests {
|
||||
release(buffer);
|
||||
}
|
||||
|
||||
|
||||
@ParameterizedDataBufferAllocatingTest
|
||||
void toByteBuffer(DataBufferFactory bufferFactory) {
|
||||
super.bufferFactory = bufferFactory;
|
||||
|
||||
DataBuffer buffer = createDataBuffer(4);
|
||||
buffer.write(new byte[]{'a', 'b', 'c'});
|
||||
buffer.read(); // skip a
|
||||
|
||||
ByteBuffer result = buffer.toByteBuffer();
|
||||
assertThat(result.capacity()).isEqualTo(2);
|
||||
assertThat(result.remaining()).isEqualTo(2);
|
||||
|
||||
byte[] resultBytes = new byte[2];
|
||||
result.get(resultBytes);
|
||||
assertThat(resultBytes).isEqualTo(new byte[]{'b', 'c'});
|
||||
|
||||
release(buffer);
|
||||
}
|
||||
|
||||
@ParameterizedDataBufferAllocatingTest
|
||||
void toByteBufferIndexLength(DataBufferFactory bufferFactory) {
|
||||
super.bufferFactory = bufferFactory;
|
||||
|
||||
DataBuffer buffer = createDataBuffer(3);
|
||||
buffer.write(new byte[]{'a', 'b', 'c'});
|
||||
|
||||
ByteBuffer result = buffer.toByteBuffer(1, 2);
|
||||
assertThat(result.capacity()).isEqualTo(2);
|
||||
assertThat(result.remaining()).isEqualTo(2);
|
||||
|
||||
byte[] resultBytes = new byte[2];
|
||||
result.get(resultBytes);
|
||||
assertThat(resultBytes).isEqualTo(new byte[]{'b', 'c'});
|
||||
|
||||
release(buffer);
|
||||
}
|
||||
|
||||
|
||||
@ParameterizedDataBufferAllocatingTest
|
||||
void indexOf(DataBufferFactory bufferFactory) {
|
||||
super.bufferFactory = bufferFactory;
|
||||
@@ -630,6 +690,7 @@ class DataBufferTests extends AbstractDataBufferAllocatingTests {
|
||||
}
|
||||
|
||||
@ParameterizedDataBufferAllocatingTest
|
||||
@SuppressWarnings("deprecation")
|
||||
void slice(DataBufferFactory bufferFactory) {
|
||||
super.bufferFactory = bufferFactory;
|
||||
|
||||
@@ -638,7 +699,6 @@ class DataBufferTests extends AbstractDataBufferAllocatingTests {
|
||||
|
||||
DataBuffer slice = buffer.slice(1, 2);
|
||||
assertThat(slice.readableByteCount()).isEqualTo(2);
|
||||
assertThatException().isThrownBy(() -> slice.write((byte) 0));
|
||||
buffer.write((byte) 'c');
|
||||
|
||||
assertThat(buffer.readableByteCount()).isEqualTo(3);
|
||||
@@ -651,13 +711,18 @@ class DataBufferTests extends AbstractDataBufferAllocatingTests {
|
||||
result = new byte[2];
|
||||
slice.read(result);
|
||||
|
||||
assertThat(result).isEqualTo(new byte[]{'b', 'c'});
|
||||
|
||||
|
||||
if (!(bufferFactory instanceof Netty5DataBufferFactory)) {
|
||||
assertThat(result).isEqualTo(new byte[]{'b', 'c'});
|
||||
}
|
||||
else {
|
||||
assertThat(result).isEqualTo(new byte[]{'b', 0});
|
||||
release(slice);
|
||||
}
|
||||
release(buffer);
|
||||
}
|
||||
|
||||
@ParameterizedDataBufferAllocatingTest
|
||||
@SuppressWarnings("deprecation")
|
||||
void retainedSlice(DataBufferFactory bufferFactory) {
|
||||
super.bufferFactory = bufferFactory;
|
||||
|
||||
@@ -666,7 +731,6 @@ class DataBufferTests extends AbstractDataBufferAllocatingTests {
|
||||
|
||||
DataBuffer slice = buffer.retainedSlice(1, 2);
|
||||
assertThat(slice.readableByteCount()).isEqualTo(2);
|
||||
assertThatException().isThrownBy(() -> slice.write((byte) 0));
|
||||
buffer.write((byte) 'c');
|
||||
|
||||
assertThat(buffer.readableByteCount()).isEqualTo(3);
|
||||
@@ -679,8 +743,12 @@ class DataBufferTests extends AbstractDataBufferAllocatingTests {
|
||||
result = new byte[2];
|
||||
slice.read(result);
|
||||
|
||||
assertThat(result).isEqualTo(new byte[]{'b', 'c'});
|
||||
|
||||
if (!(bufferFactory instanceof Netty5DataBufferFactory)) {
|
||||
assertThat(result).isEqualTo(new byte[]{'b', 'c'});
|
||||
}
|
||||
else {
|
||||
assertThat(result).isEqualTo(new byte[]{'b', 0});
|
||||
}
|
||||
|
||||
release(buffer, slice);
|
||||
}
|
||||
@@ -705,6 +773,58 @@ class DataBufferTests extends AbstractDataBufferAllocatingTests {
|
||||
release(buffer);
|
||||
}
|
||||
|
||||
@ParameterizedDataBufferAllocatingTest
|
||||
void split(DataBufferFactory bufferFactory) {
|
||||
super.bufferFactory = bufferFactory;
|
||||
|
||||
DataBuffer buffer = createDataBuffer(3);
|
||||
buffer.write(new byte[]{'a', 'b'});
|
||||
|
||||
assertThatException().isThrownBy(() -> buffer.split(-1));
|
||||
assertThatException().isThrownBy(() -> buffer.split(4));
|
||||
|
||||
DataBuffer split = buffer.split(1);
|
||||
|
||||
assertThat(split.readPosition()).isEqualTo(0);
|
||||
assertThat(split.writePosition()).isEqualTo(1);
|
||||
assertThat(split.capacity()).isEqualTo(1);
|
||||
assertThat(split.readableByteCount()).isEqualTo(1);
|
||||
byte[] bytes = new byte[1];
|
||||
split.read(bytes);
|
||||
assertThat(bytes).containsExactly('a');
|
||||
|
||||
assertThat(buffer.readPosition()).isEqualTo(0);
|
||||
assertThat(buffer.writePosition()).isEqualTo(1);
|
||||
assertThat(buffer.capacity()).isEqualTo(2);
|
||||
|
||||
buffer.write((byte) 'c');
|
||||
assertThat(buffer.readableByteCount()).isEqualTo(2);
|
||||
bytes = new byte[2];
|
||||
buffer.read(bytes);
|
||||
|
||||
assertThat(bytes).isEqualTo(new byte[]{'b', 'c'});
|
||||
|
||||
|
||||
DataBuffer buffer2 = createDataBuffer(1);
|
||||
buffer2.write(new byte[]{'a'});
|
||||
split = buffer2.split(1);
|
||||
|
||||
assertThat(split.readPosition()).isEqualTo(0);
|
||||
assertThat(split.writePosition()).isEqualTo(1);
|
||||
assertThat(split.capacity()).isEqualTo(1);
|
||||
assertThat(split.readableByteCount()).isEqualTo(1);
|
||||
bytes = new byte[1];
|
||||
split.read(bytes);
|
||||
assertThat(bytes).containsExactly('a');
|
||||
|
||||
assertThat(buffer2.readPosition()).isEqualTo(0);
|
||||
assertThat(buffer2.writePosition()).isEqualTo(0);
|
||||
assertThat(buffer2.capacity()).isEqualTo(0);
|
||||
assertThat(buffer.readableByteCount()).isEqualTo(0);
|
||||
|
||||
release(buffer, buffer2);
|
||||
}
|
||||
|
||||
@ParameterizedDataBufferAllocatingTest
|
||||
void join(DataBufferFactory bufferFactory) {
|
||||
super.bufferFactory = bufferFactory;
|
||||
|
||||
@@ -115,7 +115,7 @@ class DataBufferUtilsTests extends AbstractDataBufferAllocatingTests {
|
||||
DataBufferUtils.readByteChannel(() -> channel, super.bufferFactory, 3);
|
||||
|
||||
StepVerifier.create(result)
|
||||
.consumeNextWith(stringConsumer("foo"))
|
||||
.consumeNextWith(stringConsumer(""))
|
||||
.expectError(IOException.class)
|
||||
.verify(Duration.ofSeconds(3));
|
||||
}
|
||||
@@ -170,17 +170,15 @@ class DataBufferUtilsTests extends AbstractDataBufferAllocatingTests {
|
||||
willAnswer(invocation -> {
|
||||
ByteBuffer byteBuffer = invocation.getArgument(0);
|
||||
byteBuffer.put("foo".getBytes(StandardCharsets.UTF_8));
|
||||
byteBuffer.flip();
|
||||
long pos = invocation.getArgument(1);
|
||||
assertThat(pos).isEqualTo(0);
|
||||
DataBuffer dataBuffer = invocation.getArgument(2);
|
||||
CompletionHandler<Integer, DataBuffer> completionHandler = invocation.getArgument(3);
|
||||
completionHandler.completed(3, dataBuffer);
|
||||
CompletionHandler<Integer, ByteBuffer> completionHandler = invocation.getArgument(3);
|
||||
completionHandler.completed(3, byteBuffer);
|
||||
return null;
|
||||
}).willAnswer(invocation -> {
|
||||
DataBuffer dataBuffer = invocation.getArgument(2);
|
||||
CompletionHandler<Integer, DataBuffer> completionHandler = invocation.getArgument(3);
|
||||
completionHandler.failed(new IOException(), dataBuffer);
|
||||
ByteBuffer byteBuffer = invocation.getArgument(0);
|
||||
CompletionHandler<Integer, ByteBuffer> completionHandler = invocation.getArgument(3);
|
||||
completionHandler.failed(new IOException(), byteBuffer);
|
||||
return null;
|
||||
})
|
||||
.given(channel).read(any(), anyLong(), any(), any());
|
||||
|
||||
@@ -34,6 +34,7 @@ import io.netty.buffer.PoolArenaMetric;
|
||||
import io.netty.buffer.PooledByteBufAllocator;
|
||||
import io.netty.buffer.PooledByteBufAllocatorMetric;
|
||||
import io.netty.buffer.UnpooledByteBufAllocator;
|
||||
import io.netty5.buffer.api.BufferAllocator;
|
||||
import org.junit.jupiter.api.extension.AfterEachCallback;
|
||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
@@ -45,6 +46,7 @@ import org.springframework.core.io.buffer.DataBuffer;
|
||||
import org.springframework.core.io.buffer.DataBufferFactory;
|
||||
import org.springframework.core.io.buffer.DataBufferUtils;
|
||||
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
|
||||
import org.springframework.core.io.buffer.Netty5DataBufferFactory;
|
||||
import org.springframework.core.io.buffer.NettyDataBufferFactory;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
@@ -162,18 +164,26 @@ public abstract class AbstractDataBufferAllocatingTests {
|
||||
@SuppressWarnings("deprecation") // PooledByteBufAllocator no longer supports tinyCacheSize.
|
||||
public static Stream<Arguments> dataBufferFactories() {
|
||||
return Stream.of(
|
||||
arguments(named("NettyDataBufferFactory - UnpooledByteBufAllocator - preferDirect = true",
|
||||
new NettyDataBufferFactory(new UnpooledByteBufAllocator(true)))),
|
||||
// arguments(named("NettyDataBufferFactory - UnpooledByteBufAllocator - preferDirect = true",
|
||||
// new NettyDataBufferFactory(new UnpooledByteBufAllocator(true)))),
|
||||
arguments(named("NettyDataBufferFactory - UnpooledByteBufAllocator - preferDirect = false",
|
||||
new NettyDataBufferFactory(new UnpooledByteBufAllocator(false)))),
|
||||
// 1) Disable caching for reliable leak detection, see https://github.com/netty/netty/issues/5275
|
||||
// 2) maxOrder is 4 (vs default 11) but can be increased if necessary
|
||||
arguments(named("NettyDataBufferFactory - PooledByteBufAllocator - preferDirect = true",
|
||||
new NettyDataBufferFactory(new PooledByteBufAllocator(true, 1, 1, 4096, 4, 0, 0, 0, true)))),
|
||||
arguments(named("NettyDataBufferFactory - PooledByteBufAllocator - preferDirect = false",
|
||||
new NettyDataBufferFactory(new PooledByteBufAllocator(false, 1, 1, 4096, 4, 0, 0, 0, true)))),
|
||||
arguments(named("DefaultDataBufferFactory - preferDirect = true",
|
||||
new DefaultDataBufferFactory(true))),
|
||||
// arguments(named("NettyDataBufferFactory - PooledByteBufAllocator - preferDirect = true",
|
||||
// new NettyDataBufferFactory(new PooledByteBufAllocator(true, 1, 1, 4096, 4, 0, 0, 0, true)))),
|
||||
// arguments(named("NettyDataBufferFactory - PooledByteBufAllocator - preferDirect = false",
|
||||
// new NettyDataBufferFactory(new PooledByteBufAllocator(false, 1, 1, 4096, 4, 0, 0, 0, true)))),
|
||||
arguments(named("Netty5DataBufferFactory - BufferAllocator.onHeapUnpooled()",
|
||||
new Netty5DataBufferFactory(BufferAllocator.onHeapUnpooled()))),
|
||||
// arguments(named("Netty5DataBufferFactory - BufferAllocator.offHeapUnpooled()",
|
||||
// new Netty5DataBufferFactory(BufferAllocator.offHeapUnpooled()))),
|
||||
// arguments(named("Netty5DataBufferFactory - BufferAllocator.onHeapPooled()",
|
||||
// new Netty5DataBufferFactory(BufferAllocator.onHeapPooled()))),
|
||||
// arguments(named("Netty5DataBufferFactory - BufferAllocator.offHeapPooled()",
|
||||
// new Netty5DataBufferFactory(BufferAllocator.offHeapPooled()))),
|
||||
// arguments(named("DefaultDataBufferFactory - preferDirect = true",
|
||||
// new DefaultDataBufferFactory(true))),
|
||||
arguments(named("DefaultDataBufferFactory - preferDirect = false",
|
||||
new DefaultDataBufferFactory(false)))
|
||||
);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2022 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.
|
||||
@@ -107,6 +107,7 @@ public class LeakAwareDataBufferFactory implements DataBufferFactory {
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public DataBuffer allocateBuffer() {
|
||||
return createLeakAwareDataBuffer(this.delegate.allocateBuffer());
|
||||
}
|
||||
@@ -143,4 +144,9 @@ public class LeakAwareDataBufferFactory implements DataBufferFactory {
|
||||
return new LeakAwareDataBuffer(this.delegate.join(dataBuffers), this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDirect() {
|
||||
return this.delegate.isDirect();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user