Skip to content

Commit 3e2f58c

Browse files
committed
Offer restricted access to DataBuffer's ByteBuffer
This commit introduces DataBuffer::readableByteBuffers and DataBuffer::writableByteBuffers, allowing restricted access to the ByteBuffer used internally by DataBuffer implementations. Closes gh-29943
1 parent 72926c2 commit 3e2f58c

File tree

28 files changed

+772
-274
lines changed

28 files changed

+772
-274
lines changed

spring-core/src/main/java/org/springframework/core/codec/ByteBufferDecoder.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2022 the original author or authors.
2+
* Copyright 2002-2023 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -51,9 +51,11 @@ public boolean canDecode(ResolvableType elementType, @Nullable MimeType mimeType
5151
public ByteBuffer decode(DataBuffer dataBuffer, ResolvableType elementType,
5252
@Nullable MimeType mimeType, @Nullable Map<String, Object> hints) {
5353

54-
ByteBuffer result = dataBuffer.toByteBuffer();
54+
int len = dataBuffer.readableByteCount();
55+
ByteBuffer result = ByteBuffer.allocate(len);
56+
dataBuffer.toByteBuffer(result);
5557
if (logger.isDebugEnabled()) {
56-
logger.debug(Hints.getLogPrefix(hints) + "Read " + dataBuffer.readableByteCount() + " bytes");
58+
logger.debug(Hints.getLogPrefix(hints) + "Read " + len + " bytes");
5759
}
5860
DataBufferUtils.release(dataBuffer);
5961
return result;

spring-core/src/main/java/org/springframework/core/io/buffer/DataBuffer.java

Lines changed: 106 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2022 the original author or authors.
2+
* Copyright 2002-2023 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,14 +16,18 @@
1616

1717
package org.springframework.core.io.buffer;
1818

19+
import java.io.Closeable;
1920
import java.io.InputStream;
2021
import java.io.OutputStream;
22+
import java.io.UncheckedIOException;
2123
import java.nio.ByteBuffer;
2224
import java.nio.CharBuffer;
25+
import java.nio.charset.CharacterCodingException;
2326
import java.nio.charset.Charset;
2427
import java.nio.charset.CharsetEncoder;
2528
import java.nio.charset.CoderResult;
2629
import java.nio.charset.CodingErrorAction;
30+
import java.util.Iterator;
2731
import java.util.function.IntPredicate;
2832

2933
import org.springframework.util.Assert;
@@ -265,27 +269,28 @@ default DataBuffer write(CharSequence charSequence, Charset charset) {
265269
CharsetEncoder charsetEncoder = charset.newEncoder()
266270
.onMalformedInput(CodingErrorAction.REPLACE)
267271
.onUnmappableCharacter(CodingErrorAction.REPLACE);
268-
CharBuffer inBuffer = CharBuffer.wrap(charSequence);
269-
int estimatedSize = (int) (inBuffer.remaining() * charsetEncoder.averageBytesPerChar());
270-
ByteBuffer outBuffer = ensureCapacity(estimatedSize)
271-
.asByteBuffer(writePosition(), writableByteCount());
272-
while (true) {
273-
CoderResult cr = (inBuffer.hasRemaining() ?
274-
charsetEncoder.encode(inBuffer, outBuffer, true) : CoderResult.UNDERFLOW);
275-
if (cr.isUnderflow()) {
276-
cr = charsetEncoder.flush(outBuffer);
272+
CharBuffer src = CharBuffer.wrap(charSequence);
273+
int length = (int) (src.remaining() * charsetEncoder.maxBytesPerChar());
274+
ensureWritable(length);
275+
try (ByteBufferIterator iterator = writableByteBuffers()) {
276+
Assert.state(iterator.hasNext(), "No ByteBuffer available");
277+
ByteBuffer dest = iterator.next();
278+
int pos = dest.position();
279+
CoderResult cr = charsetEncoder.encode(src, dest, true);
280+
if (!cr.isUnderflow()) {
281+
cr.throwException();
277282
}
278-
if (cr.isUnderflow()) {
279-
break;
280-
}
281-
if (cr.isOverflow()) {
282-
writePosition(writePosition() + outBuffer.position());
283-
int maximumSize = (int) (inBuffer.remaining() * charsetEncoder.maxBytesPerChar());
284-
ensureCapacity(maximumSize);
285-
outBuffer = asByteBuffer(writePosition(), writableByteCount());
283+
cr = charsetEncoder.flush(dest);
284+
if (!cr.isUnderflow()) {
285+
cr.throwException();
286286
}
287+
length = dest.position() - pos;
288+
}
289+
catch (CharacterCodingException ex) {
290+
// should not happen, because the encoder uses action REPLACE
291+
throw new UncheckedIOException(ex);
287292
}
288-
writePosition(writePosition() + outBuffer.position());
293+
writePosition(writePosition() + length);
289294
}
290295
return this;
291296
}
@@ -353,8 +358,8 @@ default DataBuffer retainedSlice(int index, int length) {
353358
* changes in the returned buffer's {@linkplain ByteBuffer#position() position}
354359
* will not be reflected in the reading nor writing position of this data buffer.
355360
* @return this data buffer as a byte buffer
356-
* @deprecated as of 6.0, in favor of {@link #toByteBuffer()}, which does
357-
* <strong>not</strong> share data and returns a copy.
361+
* @deprecated as of 6.0, in favor of {@link #toByteBuffer(ByteBuffer)},
362+
* {@link #readableByteBuffers()}, or {@link #writableByteBuffers()}.
358363
*/
359364
@Deprecated(since = "6.0")
360365
ByteBuffer asByteBuffer();
@@ -368,8 +373,8 @@ default DataBuffer retainedSlice(int index, int length) {
368373
* @param length the length of the returned byte buffer
369374
* @return this data buffer as a byte buffer
370375
* @since 5.0.1
371-
* @deprecated as of 6.0, in favor of {@link #toByteBuffer(int, int)}, which
372-
* does <strong>not</strong> share data and returns a copy.
376+
* @deprecated as of 6.0, in favor of {@link #toByteBuffer(int, ByteBuffer, int, int)},
377+
* {@link #readableByteBuffers()}, or {@link #writableByteBuffers()}.
373378
*/
374379
@Deprecated(since = "6.0")
375380
ByteBuffer asByteBuffer(int index, int length);
@@ -380,7 +385,11 @@ default DataBuffer retainedSlice(int index, int length) {
380385
* <strong>not</strong> shared.
381386
* @return this data buffer as a byte buffer
382387
* @since 6.0
388+
* @see #readableByteBuffers()
389+
* @see #writableByteBuffers()
390+
* @deprecated as of 6.0.5, in favor of {@link #toByteBuffer(ByteBuffer)}
383391
*/
392+
@Deprecated(since = "6.0.5")
384393
default ByteBuffer toByteBuffer() {
385394
return toByteBuffer(readPosition(), readableByteCount());
386395
}
@@ -391,9 +400,67 @@ default ByteBuffer toByteBuffer() {
391400
* {@code ByteBuffer} is <strong>not</strong> shared.
392401
* @return this data buffer as a byte buffer
393402
* @since 6.0
403+
* @see #readableByteBuffers()
404+
* @see #writableByteBuffers()
405+
* @deprecated as of 6.0.5, in favor of
406+
* {@link #toByteBuffer(int, ByteBuffer, int, int)}
394407
*/
408+
@Deprecated(since = "6.0.5")
395409
ByteBuffer toByteBuffer(int index, int length);
396410

411+
/**
412+
* Copies this entire data buffer into the given destination
413+
* {@code ByteBuffer}, beginning at the current
414+
* {@linkplain #readPosition() reading position}, and the current
415+
* {@linkplain ByteBuffer#position() position} of destination byte buffer.
416+
* @param dest the destination byte buffer
417+
* @since 6.0.5
418+
*/
419+
default void toByteBuffer(ByteBuffer dest) {
420+
toByteBuffer(readPosition(), dest, dest.position(), readableByteCount());
421+
}
422+
423+
/**
424+
* Copies the given length from this data buffer into the given destination
425+
* {@code ByteBuffer}, beginning at the given source position, and the
426+
* given destination position in the destination byte buffer.
427+
* @param srcPos the position of this data buffer from where copying should
428+
* start
429+
* @param dest the destination byte buffer
430+
* @param destPos the position in {@code dest} to where copying should
431+
* start
432+
* @param length the amount of data to copy
433+
* @since 6.0.5
434+
*/
435+
void toByteBuffer(int srcPos, ByteBuffer dest, int destPos, int length);
436+
437+
/**
438+
* Returns a closeable iterator over each {@link ByteBuffer} in this data
439+
* buffer that can be read. Calling this method is more efficient than
440+
* {@link #toByteBuffer()}, as no data is copied. However, the byte buffers
441+
* provided can only be used during the iteration.
442+
*
443+
* <p><b>Note</b> that the returned iterator must be used in a
444+
* try-with-resources clause or explicitly
445+
* {@linkplain ByteBufferIterator#close() closed}.
446+
* @return a closeable iterator over the readable byte buffers contained in this data buffer
447+
* @since 6.0.5
448+
*/
449+
ByteBufferIterator readableByteBuffers();
450+
451+
/**
452+
* Returns a closeable iterator over each {@link ByteBuffer} in this data
453+
* buffer that can be written to. The byte buffers provided can only be used
454+
* during the iteration.
455+
*
456+
* <p><b>Note</b> that the returned iterator must be used in a
457+
* try-with-resources clause or explicitly
458+
* {@linkplain ByteBufferIterator#close() closed}.
459+
* @return a closeable iterator over the writable byte buffers contained in this data buffer
460+
* @since 6.0.5
461+
*/
462+
ByteBufferIterator writableByteBuffers();
463+
397464
/**
398465
* Expose this buffer's data as an {@link InputStream}. Both data and read position are
399466
* shared between the returned stream and this data buffer. The underlying buffer will
@@ -450,4 +517,20 @@ default String toString(Charset charset) {
450517
*/
451518
String toString(int index, int length, Charset charset);
452519

520+
521+
/**
522+
* A dedicated iterator type that ensures the lifecycle of iterated
523+
* {@link ByteBuffer} elements. This iterator must be used in a
524+
* try-with-resources clause or explicitly {@linkplain #close() closed}.
525+
*
526+
* @see DataBuffer#readableByteBuffers()
527+
* @see DataBuffer#writableByteBuffers()
528+
*/
529+
interface ByteBufferIterator extends Iterator<ByteBuffer>, Closeable {
530+
531+
@Override
532+
void close();
533+
534+
}
535+
453536
}

0 commit comments

Comments
 (0)