|
19 | 19 |
|
20 | 20 | import java.nio.ByteBuffer;
|
21 | 21 | import java.util.ArrayList;
|
| 22 | +import java.util.Collections; |
22 | 23 | import java.util.List;
|
23 | 24 | import java.util.concurrent.atomic.AtomicLong;
|
24 | 25 | import software.amazon.awssdk.annotations.SdkInternalApi;
|
25 |
| -import software.amazon.awssdk.utils.BinaryUtils; |
| 26 | +import software.amazon.awssdk.utils.Logger; |
26 | 27 | import software.amazon.awssdk.utils.Validate;
|
27 | 28 | import software.amazon.awssdk.utils.builder.SdkBuilder;
|
28 | 29 |
|
|
31 | 32 | */
|
32 | 33 | @SdkInternalApi
|
33 | 34 | public final class ChunkBuffer {
|
34 |
| - private final AtomicLong remainingBytes; |
| 35 | + private static final Logger log = Logger.loggerFor(ChunkBuffer.class); |
| 36 | + private final AtomicLong transferredBytes; |
35 | 37 | private final ByteBuffer currentBuffer;
|
36 |
| - private final int bufferSize; |
| 38 | + private final int chunkSize; |
| 39 | + private final long totalBytes; |
37 | 40 |
|
38 | 41 | private ChunkBuffer(Long totalBytes, Integer bufferSize) {
|
39 | 42 | Validate.notNull(totalBytes, "The totalBytes must not be null");
|
40 | 43 |
|
41 | 44 | int chunkSize = bufferSize != null ? bufferSize : DEFAULT_ASYNC_CHUNK_SIZE;
|
42 |
| - this.bufferSize = chunkSize; |
| 45 | + this.chunkSize = chunkSize; |
43 | 46 | this.currentBuffer = ByteBuffer.allocate(chunkSize);
|
44 |
| - this.remainingBytes = new AtomicLong(totalBytes); |
| 47 | + this.totalBytes = totalBytes; |
| 48 | + this.transferredBytes = new AtomicLong(0); |
45 | 49 | }
|
46 | 50 |
|
47 | 51 | public static Builder builder() {
|
48 | 52 | return new DefaultBuilder();
|
49 | 53 | }
|
50 | 54 |
|
51 | 55 |
|
52 |
| - // currentBuffer and bufferedList can get over written if concurrent Threads calls this method at the same time. |
53 |
| - public synchronized Iterable<ByteBuffer> bufferAndCreateChunks(ByteBuffer buffer) { |
54 |
| - int startPosition = 0; |
55 |
| - List<ByteBuffer> bufferedList = new ArrayList<>(); |
56 |
| - int currentBytesRead = buffer.remaining(); |
57 |
| - do { |
58 |
| - int bufferedBytes = currentBuffer.position(); |
59 |
| - int availableToRead = bufferSize - bufferedBytes; |
60 |
| - int bytesToMove = Math.min(availableToRead, currentBytesRead - startPosition); |
| 56 | + /** |
| 57 | + * Split the input {@link ByteBuffer} into multiple smaller {@link ByteBuffer}s, each of which contains {@link #chunkSize} |
| 58 | + * worth of bytes. If the last chunk of the input ByteBuffer contains less than {@link #chunkSize} data, the last chunk will |
| 59 | + * be buffered. |
| 60 | + */ |
| 61 | + public synchronized Iterable<ByteBuffer> split(ByteBuffer inputByteBuffer) { |
61 | 62 |
|
62 |
| - byte[] bytes = BinaryUtils.copyAllBytesFrom(buffer); |
63 |
| - if (bufferedBytes == 0) { |
64 |
| - currentBuffer.put(bytes, startPosition, bytesToMove); |
65 |
| - } else { |
66 |
| - currentBuffer.put(bytes, 0, bytesToMove); |
| 63 | + if (!inputByteBuffer.hasRemaining()) { |
| 64 | + return Collections.singletonList(inputByteBuffer); |
| 65 | + } |
| 66 | + |
| 67 | + List<ByteBuffer> byteBuffers = new ArrayList<>(); |
| 68 | + |
| 69 | + // If current buffer is not empty, fill the buffer first. |
| 70 | + if (currentBuffer.position() != 0) { |
| 71 | + fillCurrentBuffer(inputByteBuffer); |
| 72 | + |
| 73 | + if (isCurrentBufferFull()) { |
| 74 | + addCurrentBufferToIterable(byteBuffers, chunkSize); |
| 75 | + } |
| 76 | + } |
| 77 | + |
| 78 | + // If the input buffer is not empty, split the input buffer |
| 79 | + if (inputByteBuffer.hasRemaining()) { |
| 80 | + splitRemainingInputByteBuffer(inputByteBuffer, byteBuffers); |
| 81 | + } |
| 82 | + |
| 83 | + // If this is the last chunk, add data buffered to the iterable |
| 84 | + if (isLastChunk()) { |
| 85 | + int remainingBytesInBuffer = currentBuffer.position(); |
| 86 | + addCurrentBufferToIterable(byteBuffers, remainingBytesInBuffer); |
| 87 | + } |
| 88 | + return byteBuffers; |
| 89 | + } |
| 90 | + |
| 91 | + private boolean isCurrentBufferFull() { |
| 92 | + return currentBuffer.position() == chunkSize; |
| 93 | + } |
| 94 | + |
| 95 | + /** |
| 96 | + * Splits the input ByteBuffer to multiple chunks and add them to the iterable. |
| 97 | + */ |
| 98 | + private void splitRemainingInputByteBuffer(ByteBuffer inputByteBuffer, List<ByteBuffer> byteBuffers) { |
| 99 | + while (inputByteBuffer.hasRemaining()) { |
| 100 | + ByteBuffer inputByteBufferCopy = inputByteBuffer.asReadOnlyBuffer(); |
| 101 | + if (inputByteBuffer.remaining() < chunkSize) { |
| 102 | + currentBuffer.put(inputByteBuffer); |
| 103 | + break; |
67 | 104 | }
|
68 | 105 |
|
69 |
| - startPosition = startPosition + bytesToMove; |
70 |
| - |
71 |
| - // Send the data once the buffer is full |
72 |
| - if (currentBuffer.position() == bufferSize) { |
73 |
| - currentBuffer.position(0); |
74 |
| - ByteBuffer bufferToSend = ByteBuffer.allocate(bufferSize); |
75 |
| - bufferToSend.put(currentBuffer.array(), 0, bufferSize); |
76 |
| - bufferToSend.clear(); |
77 |
| - currentBuffer.clear(); |
78 |
| - bufferedList.add(bufferToSend); |
79 |
| - remainingBytes.addAndGet(-bufferSize); |
| 106 | + int newLimit = inputByteBufferCopy.position() + chunkSize; |
| 107 | + inputByteBufferCopy.limit(newLimit); |
| 108 | + inputByteBuffer.position(newLimit); |
| 109 | + byteBuffers.add(inputByteBufferCopy); |
| 110 | + transferredBytes.addAndGet(chunkSize); |
| 111 | + } |
| 112 | + } |
| 113 | + |
| 114 | + private boolean isLastChunk() { |
| 115 | + long remainingBytes = totalBytes - transferredBytes.get(); |
| 116 | + return remainingBytes != 0 && remainingBytes == currentBuffer.position(); |
| 117 | + } |
| 118 | + |
| 119 | + private void addCurrentBufferToIterable(List<ByteBuffer> byteBuffers, int capacity) { |
| 120 | + ByteBuffer bufferedChunk = ByteBuffer.allocate(capacity); |
| 121 | + currentBuffer.flip(); |
| 122 | + bufferedChunk.put(currentBuffer); |
| 123 | + bufferedChunk.flip(); |
| 124 | + byteBuffers.add(bufferedChunk); |
| 125 | + transferredBytes.addAndGet(bufferedChunk.remaining()); |
| 126 | + currentBuffer.clear(); |
| 127 | + } |
| 128 | + |
| 129 | + private void fillCurrentBuffer(ByteBuffer inputByteBuffer) { |
| 130 | + while (currentBuffer.position() < chunkSize) { |
| 131 | + if (!inputByteBuffer.hasRemaining()) { |
| 132 | + break; |
| 133 | + } |
| 134 | + |
| 135 | + int remainingCapacity = chunkSize - currentBuffer.position(); |
| 136 | + |
| 137 | + if (inputByteBuffer.remaining() < remainingCapacity) { |
| 138 | + currentBuffer.put(inputByteBuffer); |
| 139 | + } else { |
| 140 | + ByteBuffer remainingChunk = inputByteBuffer.asReadOnlyBuffer(); |
| 141 | + int newLimit = inputByteBuffer.position() + remainingCapacity; |
| 142 | + remainingChunk.limit(newLimit); |
| 143 | + inputByteBuffer.position(newLimit); |
| 144 | + currentBuffer.put(remainingChunk); |
80 | 145 | }
|
81 |
| - } while (startPosition < currentBytesRead); |
82 |
| - |
83 |
| - int remainingBytesInBuffer = currentBuffer.position(); |
84 |
| - |
85 |
| - // Send the remaining buffer when |
86 |
| - // 1. remainingBytes in buffer are same as the last few bytes to be read. |
87 |
| - // 2. If it is a zero byte and the last byte to be read. |
88 |
| - if (remainingBytes.get() == remainingBytesInBuffer && |
89 |
| - (buffer.remaining() == 0 || remainingBytesInBuffer > 0)) { |
90 |
| - currentBuffer.clear(); |
91 |
| - ByteBuffer trimmedBuffer = ByteBuffer.allocate(remainingBytesInBuffer); |
92 |
| - trimmedBuffer.put(currentBuffer.array(), 0, remainingBytesInBuffer); |
93 |
| - trimmedBuffer.clear(); |
94 |
| - bufferedList.add(trimmedBuffer); |
95 |
| - remainingBytes.addAndGet(-remainingBytesInBuffer); |
96 | 146 | }
|
97 |
| - return bufferedList; |
98 | 147 | }
|
99 | 148 |
|
100 | 149 | public interface Builder extends SdkBuilder<Builder, ChunkBuffer> {
|
|
0 commit comments