View Javadoc
1   /*
2    * Copyright 2021 The Netty Project
3    *
4    * The Netty Project licenses this file to you under the Apache License,
5    * version 2.0 (the "License"); you may not use this file except in compliance
6    * with the License. You may obtain a copy of the License at:
7    *
8    *   https://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations
14   * under the License.
15   */
16  package io.netty5.buffer.api;
17  
18  import io.netty5.buffer.api.ComponentIterator.Next;
19  import io.netty5.buffer.api.internal.ResourceSupport;
20  import io.netty5.buffer.api.internal.Statics;
21  import io.netty5.util.SafeCloseable;
22  import io.netty5.util.Send;
23  
24  import java.io.IOException;
25  import java.nio.ByteBuffer;
26  import java.nio.ReadOnlyBufferException;
27  import java.nio.channels.FileChannel;
28  import java.nio.channels.GatheringByteChannel;
29  import java.nio.channels.ReadableByteChannel;
30  import java.nio.channels.ScatteringByteChannel;
31  import java.nio.channels.WritableByteChannel;
32  import java.util.ArrayList;
33  import java.util.Arrays;
34  import java.util.Collection;
35  import java.util.IdentityHashMap;
36  import java.util.Iterator;
37  import java.util.List;
38  import java.util.Map;
39  import java.util.NoSuchElementException;
40  import java.util.Objects;
41  import java.util.function.Function;
42  
43  import static io.netty5.buffer.api.internal.Statics.MAX_BUFFER_SIZE;
44  import static io.netty5.buffer.api.internal.Statics.bufferIsClosed;
45  import static io.netty5.buffer.api.internal.Statics.bufferIsReadOnly;
46  import static io.netty5.buffer.api.internal.Statics.checkImplicitCapacity;
47  import static io.netty5.buffer.api.internal.Statics.checkLength;
48  import static io.netty5.util.internal.ObjectUtil.checkPositiveOrZero;
49  import static io.netty5.util.internal.PlatformDependent.roundToPowerOfTwo;
50  import static java.lang.Math.addExact;
51  import static java.lang.Math.toIntExact;
52  
53  /**
54   * The default implementation of the {@link CompositeBuffer} interface.
55   */
56  final class DefaultCompositeBuffer extends ResourceSupport<Buffer, DefaultCompositeBuffer> implements CompositeBuffer {
57      private static final Drop<DefaultCompositeBuffer> COMPOSITE_DROP = new Drop<>() {
58          @Override
59          public void drop(DefaultCompositeBuffer buf) {
60              RuntimeException re = null;
61              for (Buffer b : buf.bufs) {
62                  try {
63                      b.close();
64                  } catch (RuntimeException e) {
65                      if (re == null) {
66                          re = e;
67                      } else {
68                          re.addSuppressed(e);
69                      }
70                  }
71              }
72          }
73  
74          @Override
75          public Drop<DefaultCompositeBuffer> fork() {
76              return this;
77          }
78  
79          @Override
80          public void attach(DefaultCompositeBuffer obj) {
81          }
82  
83          @Override
84          public String toString() {
85              return "COMPOSITE_DROP";
86          }
87      };
88      private static final Buffer[] EMPTY_BUFFER_ARRAY = new Buffer[0];
89      /**
90       * The various write*() methods may automatically grow the buffer, if there is not enough capacity to service the
91       * write operation.
92       * When this happens, we have to choose some reasonable amount to grow the buffer by. For normal buffers, we just
93       * double their capacity to amortise this cost. For composite buffers, this doubling doesn't quite make as much
94       * sense, as we are adding components rather than reallocating the entire innards of the buffer.
95       * In the normal case, the composite buffer will grow by the average size of its components.
96       * However, when the composite buffer is empty, we have no basis on which to compute an average.
97       * We have to pick a number out of thin air.
98       * We have chosen a size of 256 bytes for this purpose. This is the same as the initial capacity used, when
99       * allocating a ByteBuf of an unspecified size, in Netty 4.1.
100      */
101     private static final int FIRST_AUTOMATIC_COMPONENT_SIZE = 256;
102 
103     private final BufferAllocator allocator;
104     private final TornBufferAccessor tornBufAccessors;
105     private Buffer[] bufs;
106     private int[] offsets; // The offset, for the composite buffer, where each constituent buffer starts.
107     private int capacity;
108     private int roff;
109     private int woff;
110     private int subOffset; // The next offset *within* a constituent buffer to read from or write to.
111     private boolean closed;
112     private boolean readOnly;
113     private int implicitCapacityLimit;
114 
115     /**
116      * @see BufferAllocator#compose(Iterable)
117      */
118     public static CompositeBuffer compose(BufferAllocator allocator, Iterable<Send<Buffer>> sends) {
119         final List<Buffer> bufs;
120         if (sends instanceof Collection) {
121             bufs = new ArrayList<>(((Collection<?>) sends).size());
122         } else {
123             bufs = new ArrayList<>(4);
124         }
125         RuntimeException receiveException = null;
126         for (Send<Buffer> buf: sends) {
127             if (receiveException != null) {
128                 try {
129                     buf.close();
130                 } catch (Exception closeExc) {
131                     receiveException.addSuppressed(closeExc);
132                 }
133             } else {
134                 try {
135                     bufs.add(buf.receive());
136                 } catch (RuntimeException e) {
137                     // We catch RuntimeException instead of IllegalStateException to ensure cleanup always happens
138                     // regardless of the exception thrown.
139                     receiveException = e;
140                     for (Buffer b: bufs) {
141                         try {
142                             b.close();
143                         } catch (Exception closeExc) {
144                             receiveException.addSuppressed(closeExc);
145                         }
146                     }
147                 }
148             }
149         }
150         if (receiveException != null) {
151             throw receiveException;
152         }
153         return new DefaultCompositeBuffer(allocator, filterExternalBufs(bufs), COMPOSITE_DROP);
154     }
155 
156     /**
157      * @see CompositeBuffer#compose(BufferAllocator)
158      */
159     public static CompositeBuffer compose(BufferAllocator allocator) {
160         return new DefaultCompositeBuffer(allocator, EMPTY_BUFFER_ARRAY, COMPOSITE_DROP);
161     }
162 
163     private static Buffer[] filterExternalBufs(Iterable<Buffer> externals) {
164         // We filter out all zero-capacity buffers because they wouldn't contribute to the composite buffer anyway,
165         // and also, by ensuring that all constituent buffers contribute to the size of the composite buffer,
166         // we make sure the number of composite buffers will never become greater than the number of bytes in
167         // the composite buffer.
168         // We also filter out middle buffers that don't contribute any readable bytes, due to their trimming.
169         // This restriction guarantees that methods like countComponents, forEachReadable and forEachWritable,
170         // will never overflow their component counts.
171         // Allocating a new array unconditionally also prevents external modification of the array.
172         Collector collector = new Collector(externals);
173         collector.collect(externals);
174         return collector.toArray();
175     }
176 
177     private static final class Collector {
178         private Buffer[] array;
179         private int index;
180 
181         Collector(Iterable<Buffer> externals) {
182             final Map<Buffer, Buffer> dupeCheck;
183             if (externals instanceof Collection) {
184                 dupeCheck = new IdentityHashMap<>(((Collection<?>) externals).size());
185             } else {
186                 dupeCheck = new IdentityHashMap<>();
187             }
188             int size = 0;
189             for (Buffer buf : externals) {
190                 if (dupeCheck.put(buf, buf) != null) {
191                     // Throw if there are duplicates among the buffers.
192                     // We can do this before we decompose composites, because the components are owned,
193                     // and no two composite buffers can have the same components, unless they violate
194                     // their API contract.
195                     closeAllAndThrowDupeException(externals);
196                 }
197                 size += buf.countComponents();
198             }
199             array = new Buffer[size];
200         }
201 
202         private static void closeAllAndThrowDupeException(Iterable<Buffer> externals) {
203             IllegalArgumentException iae = new IllegalArgumentException("Cannot compose duplicate buffers.");
204             for (Buffer toClose : externals) {
205                 try {
206                     toClose.close();
207                 } catch (Exception closeExc) {
208                     iae.addSuppressed(closeExc);
209                 }
210             }
211             throw iae;
212         }
213 
214         void add(Buffer buffer) {
215             if (index == array.length) {
216                 array = Arrays.copyOf(array, array.length * 2);
217             }
218             array[index] = buffer;
219             index++;
220         }
221 
222         void collect(Iterable<Buffer> externals) {
223             for (Buffer buf : externals) {
224                 if (buf.capacity() == 0) {
225                     buf.close();
226                 } else if (CompositeBuffer.isComposite(buf)) {
227                     CompositeBuffer cbuf = (CompositeBuffer) buf;
228                     collect(Arrays.asList(cbuf.decomposeBuffer()));
229                 } else {
230                     add(buf);
231                 }
232             }
233         }
234 
235         Buffer[] toArray() {
236             int firstReadable = -1;
237             int lastReadable = -1;
238             for (int i = 0; i < index; i++) {
239                 if (array[i].readableBytes() != 0) {
240                     if (firstReadable == -1) {
241                         firstReadable = i;
242                     }
243                     lastReadable = i;
244                 }
245             }
246             // Bytes already read, in components after the first readable section, must be trimmed off.
247             // Likewise, writable bytes prior to the last readable section must be trimmed off.
248             if (firstReadable != -1) {
249                 // Remove middle buffers entirely that have no readable bytes.
250                 for (int i = firstReadable + 1; i < lastReadable; i++) {
251                     Buffer buf = array[i];
252                     if (buf.readableBytes() == 0) {
253                         buf.close();
254                         if (i <= index - 2) {
255                             System.arraycopy(array, i + 1, array, i, index - i - 1);
256                         }
257                         i--;
258                         index--;
259                         lastReadable--;
260                     }
261                 }
262                 // Remove already-read bytes from middle and end buffers.
263                 for (int i = firstReadable + 1; i < index; i++) {
264                     Buffer buf = array[i];
265                     if (buf.readerOffset() > 0) {
266                         buf.readSplit(0).close();
267                     }
268                 }
269                 // Remove writable-bytes from front and middle buffers.
270                 for (int i = 0; i < lastReadable; i++) {
271                     Buffer buf = array[i];
272                     if (buf.writableBytes() > 0) {
273                         array[i] = buf.split();
274                         buf.close();
275                     }
276                 }
277             }
278             return array.length == index? array : Arrays.copyOf(array, index);
279         }
280     }
281 
282     private static final class ConcatIterable<T> implements Iterable<T> {
283         private final Iterable<T> first;
284         private final Iterable<T> second;
285 
286         ConcatIterable(Iterable<T> first, Iterable<T> second) {
287             this.first = first;
288             this.second = second;
289         }
290 
291         @Override
292         public Iterator<T> iterator() {
293             return new ConcatIterator<>(first.iterator(), second.iterator());
294         }
295     }
296 
297     private static final class ConcatIterator<T> implements Iterator<T> {
298         private Iterator<T> current;
299         private Iterator<T> next;
300 
301         ConcatIterator(Iterator<T> first, Iterator<T> second) {
302             current = first;
303             next = second;
304         }
305 
306         @Override
307         public boolean hasNext() {
308             return current != null && current.hasNext() || next != null && next.hasNext();
309         }
310 
311         @Override
312         public T next() {
313             while (current != null) {
314                 if (current.hasNext()) {
315                     return current.next();
316                 }
317                 current = next;
318                 next = null;
319             }
320             throw new NoSuchElementException();
321         }
322     }
323 
324     private DefaultCompositeBuffer(BufferAllocator allocator, Buffer[] bufs, Drop<DefaultCompositeBuffer> drop) {
325         super(drop);
326         try {
327             this.allocator = Objects.requireNonNull(allocator, "BufferAllocator cannot be null.");
328             if (bufs.length > 0) {
329                 boolean targetReadOnly = bufs[0].readOnly();
330                 for (Buffer buf : bufs) {
331                     if (buf.readOnly() != targetReadOnly) {
332                         throw new IllegalArgumentException("Constituent buffers have inconsistent read-only state.");
333                     }
334                 }
335                 readOnly = targetReadOnly;
336             }
337             this.bufs = bufs;
338             computeBufferOffsets();
339             implicitCapacityLimit = MAX_BUFFER_SIZE;
340             tornBufAccessors = new TornBufferAccessor(this);
341         } catch (Exception e) {
342             // Always close bufs on exception, since we've taken ownership of them at this point.
343             for (Buffer buf : bufs) {
344                 try {
345                     buf.close();
346                 } catch (Exception closeExc) {
347                     e.addSuppressed(closeExc);
348                 }
349             }
350             throw e;
351         }
352     }
353 
354     private void computeBufferOffsets() {
355         int woff = 0;
356         int roff = 0;
357         if (bufs.length > 0) {
358             boolean woffMidpoint = false;
359             for (Buffer buf : bufs) {
360                 if (!woffMidpoint) {
361                     // First region, before the composite writer-offset.
362                     woff += buf.writerOffset();
363                     if (buf.writableBytes() > 0) {
364                         // We have writable bytes, so the composite writer-offset is in here.
365                         woffMidpoint = true;
366                     }
367                 } else if (buf.writerOffset() != 0) {
368                     // We're past the composite write-offset, so all component writer-offsets must be zero from here.
369                     throw new AssertionError(
370                             "The given buffers cannot be composed because they leave an unwritten gap: " +
371                             Arrays.toString(bufs) + '.');
372                 }
373             }
374             boolean roffMidpoint = false;
375             for (Buffer buf : bufs) {
376                 if (!roffMidpoint) {
377                     // First region, before we've found the composite reader-offset.
378                     roff += buf.readerOffset();
379                     if (buf.readableBytes() > 0 || buf.writableBytes() > 0) {
380                         roffMidpoint = true;
381                     }
382                 } else if (buf.readerOffset() != 0) {
383                     throw new AssertionError(
384                             "The given buffers cannot be composed because they leave an unread gap: " +
385                             Arrays.toString(bufs) + '.');
386                 }
387             }
388         }
389         // Commit computed offsets, if any
390         this.woff = woff;
391         this.roff = roff;
392 
393         offsets = new int[bufs.length];
394         long cap = 0;
395         for (int i = 0; i < bufs.length; i++) {
396             offsets[i] = (int) cap;
397             cap += bufs[i].capacity();
398         }
399         if (cap > MAX_BUFFER_SIZE) {
400             throw new IllegalArgumentException(
401                     "Combined size of the constituent buffers is too big. " +
402                     "The maximum buffer capacity is " + MAX_BUFFER_SIZE + " (Integer.MAX_VALUE - 8), " +
403                     "but the sum of the constituent buffer capacities was " + cap + '.');
404         }
405         capacity = (int) cap;
406     }
407 
408     @Override
409     public String toString() {
410         return "Buffer[roff:" + roff + ", woff:" + woff + ", cap:" + capacity + ']';
411     }
412 
413     @Override
414     protected RuntimeException createResourceClosedException() {
415         return bufferIsClosed(this);
416     }
417 
418     @Override
419     public int capacity() {
420         return capacity;
421     }
422 
423     @Override
424     public int readerOffset() {
425         return roff;
426     }
427 
428     @Override
429     public CompositeBuffer readerOffset(int index) {
430         checkReadBounds(index, 0);
431         int indexLeft = index;
432         for (Buffer buf : bufs) {
433             buf.readerOffset(Math.min(indexLeft, buf.capacity()));
434             indexLeft = Math.max(0, indexLeft - buf.capacity());
435         }
436         roff = index;
437         return this;
438     }
439 
440     @Override
441     public int writerOffset() {
442         return woff;
443     }
444 
445     @Override
446     public CompositeBuffer writerOffset(int index) {
447         checkWriteBounds(index, 0);
448         int indexLeft = index;
449         for (Buffer buf : bufs) {
450             buf.writerOffset(Math.min(indexLeft, buf.capacity()));
451             indexLeft = Math.max(0, indexLeft - buf.capacity());
452         }
453         woff = index;
454         return this;
455     }
456 
457     @Override
458     public CompositeBuffer fill(byte value) {
459         if (closed) {
460             throw bufferIsClosed(this);
461         }
462         for (Buffer buf : bufs) {
463             buf.fill(value);
464         }
465         return this;
466     }
467 
468     @Override
469     public CompositeBuffer makeReadOnly() {
470         for (Buffer buf : bufs) {
471             buf.makeReadOnly();
472         }
473         readOnly = true;
474         return this;
475     }
476 
477     @Override
478     public boolean readOnly() {
479         return readOnly;
480     }
481 
482     @Override
483     public boolean isDirect() {
484         // A composite buffer is direct, if all components are direct.
485         for (Buffer buf : bufs) {
486             if (!buf.isDirect()) {
487                 return false;
488             }
489         }
490         return true;
491     }
492 
493     @Override
494     public CompositeBuffer implicitCapacityLimit(int limit) {
495         checkImplicitCapacity(limit,  capacity());
496         implicitCapacityLimit = limit;
497         return this;
498     }
499 
500     @Override
501     public int implicitCapacityLimit() {
502         return implicitCapacityLimit;
503     }
504 
505     @Override
506     public CompositeBuffer copy(int offset, int length, boolean readOnly) {
507         checkLength(length);
508         checkGetBounds(offset, length);
509         if (closed) {
510             throw bufferIsClosed(this);
511         }
512         Buffer[] copies;
513 
514         if (bufs.length == 0) {
515             // Specialise for the empty buffer.
516             assert length == 0 && offset == 0;
517             copies = bufs;
518         } else {
519             Buffer choice = (Buffer) chooseBuffer(offset, 0);
520             if (length > 0) {
521                 copies = new Buffer[bufs.length];
522                 int off = subOffset;
523                 int cap = length;
524                 int i;
525                 int j = 0;
526                 for (i = searchOffsets(offset); cap > 0; i++) {
527                     var buf = bufs[i];
528                     int avail = buf.capacity() - off;
529                     copies[j++] = buf.copy(off, Math.min(cap, avail), readOnly);
530                     cap -= avail;
531                     off = 0;
532                 }
533                 copies = Arrays.copyOf(copies, j);
534             } else {
535                 // Specialize for length == 0.
536                 copies = new Buffer[] { choice.copy(subOffset, 0) };
537             }
538         }
539 
540         return new DefaultCompositeBuffer(allocator, copies, COMPOSITE_DROP);
541     }
542 
543     @Override
544     public void copyInto(int srcPos, byte[] dest, int destPos, int length) {
545         copyInto(srcPos, (s, b, d, l) -> b.copyInto(s, dest, d, l), destPos, length);
546     }
547 
548     @Override
549     public void copyInto(int srcPos, ByteBuffer dest, int destPos, int length) {
550         if (dest.isReadOnly()) {
551             throw new ReadOnlyBufferException();
552         }
553         copyInto(srcPos, (s, b, d, l) -> b.copyInto(s, dest, d, l), destPos, length);
554     }
555 
556     private void copyInto(int srcPos, CopyInto dest, int destPos, int length) {
557         if (!isAccessible()) {
558             throw attachTrace(bufferIsClosed(this));
559         }
560         if (length < 0) {
561             throw new IndexOutOfBoundsException("Length cannot be negative: " + length + '.');
562         }
563         if (srcPos < 0) {
564             throw indexOutOfBounds(srcPos, false);
565         }
566         if (srcPos + length > capacity) {
567             throw indexOutOfBounds(srcPos + length, false);
568         }
569         while (length > 0) {
570             var buf = (Buffer) chooseBuffer(srcPos, 0);
571             int toCopy = Math.min(buf.capacity() - subOffset, length);
572             dest.copyInto(subOffset, buf, destPos, toCopy);
573             srcPos += toCopy;
574             destPos += toCopy;
575             length -= toCopy;
576         }
577     }
578 
579     @FunctionalInterface
580     private interface CopyInto {
581         void copyInto(int srcPos, Buffer src, int destPos, int length);
582     }
583 
584     @Override
585     public void copyInto(int srcPos, Buffer dest, int destPos, int length) {
586         if (!isAccessible()) {
587             throw attachTrace(bufferIsClosed(this));
588         }
589         if (length < 0) {
590             throw new IndexOutOfBoundsException("Length cannot be negative: " + length + '.');
591         }
592         if (srcPos < 0) {
593             throw indexOutOfBounds(srcPos, false);
594         }
595         if (addExact(srcPos, length) > capacity) {
596             throw indexOutOfBounds(srcPos + length, false);
597         }
598         if (dest.readOnly()) {
599             throw bufferIsReadOnly(dest);
600         }
601         if (length == 0) {
602             return;
603         }
604 
605         // Iterate in reverse to account for src and dest buffer overlap.
606         // todo optimise by delegating to constituent buffers.
607         var cursor = openReverseCursor(srcPos + length - 1, length);
608         while (cursor.readByte()) {
609             dest.setByte(destPos + --length, cursor.getByte());
610         }
611     }
612 
613     @Override
614     public int transferTo(WritableByteChannel channel, int length) throws IOException {
615         if (!isAccessible()) {
616             throw bufferIsClosed(this);
617         }
618         length = Math.min(readableBytes(), length);
619         if (length == 0) {
620             return 0;
621         }
622         checkReadBounds(readerOffset(), length);
623         ByteBufferCollector collector = new ByteBufferCollector(countReadableComponents());
624         forEachReadable(0, collector);
625         ByteBuffer[] byteBuffers = collector.buffers;
626         int bufferCount = countAndPrepareBuffersForChannelIO(length, byteBuffers);
627         int totalBytesWritten = 0;
628         try {
629             if (channel instanceof GatheringByteChannel) {
630                 GatheringByteChannel gatheringChannel = (GatheringByteChannel) channel;
631                 totalBytesWritten = toIntExact(gatheringChannel.write(byteBuffers, 0, bufferCount));
632             } else {
633                 for (int i = 0; i < bufferCount; i++) {
634                     int bytesWritten = channel.write(byteBuffers[i]);
635                     totalBytesWritten = addExact(totalBytesWritten, bytesWritten);
636                 }
637             }
638         } finally {
639             skipReadableBytes(totalBytesWritten);
640         }
641         return totalBytesWritten;
642     }
643 
644     @Override
645     public int transferFrom(FileChannel channel, long position, int length) throws IOException {
646         checkPositiveOrZero(position, "position");
647         checkPositiveOrZero(length, "length");
648         if (!isAccessible()) {
649             throw bufferIsClosed(this);
650         }
651         if (readOnly()) {
652             throw bufferIsReadOnly(this);
653         }
654         length = Math.min(writableBytes(), length);
655         if (length == 0) {
656             return 0;
657         }
658         checkWriteBounds(writerOffset(), length);
659         ByteBufferCollector collector = new ByteBufferCollector(countWritableComponents());
660         forEachWritable(0, collector);
661         ByteBuffer[] byteBuffers = collector.buffers;
662         int bufferCount = countAndPrepareBuffersForChannelIO(length, byteBuffers);
663         int totalBytesRead = 0;
664         try {
665             for (int i = 0; i < bufferCount; i++) {
666                 int bytesRead = channel.read(byteBuffers[i], position + totalBytesRead);
667                 if (bytesRead == -1) {
668                     if (i == 0) {
669                         return -1; // If we're end-of-stream on the first read, immediately return -1.
670                     }
671                     break;
672                 }
673                 totalBytesRead = addExact(totalBytesRead, bytesRead);
674             }
675         } finally {
676             if (totalBytesRead > 0) { // Don't skipWritable if total is 0 or -1
677                 skipWritableBytes(totalBytesRead);
678             }
679         }
680         return totalBytesRead;
681     }
682 
683     @Override
684     public int transferFrom(ReadableByteChannel channel, int length) throws IOException {
685         if (!isAccessible()) {
686             throw bufferIsClosed(this);
687         }
688         if (readOnly()) {
689             throw bufferIsReadOnly(this);
690         }
691         length = Math.min(writableBytes(), length);
692         if (length == 0) {
693             return 0;
694         }
695         checkWriteBounds(writerOffset(), length);
696         ByteBufferCollector collector = new ByteBufferCollector(countWritableComponents());
697         forEachWritable(0, collector);
698         ByteBuffer[] byteBuffers = collector.buffers;
699         int bufferCount = countAndPrepareBuffersForChannelIO(length, byteBuffers);
700         int totalBytesRead = 0;
701         try {
702             if (channel instanceof ScatteringByteChannel) {
703                 ScatteringByteChannel scatteringChannel = (ScatteringByteChannel) channel;
704                 totalBytesRead = toIntExact(scatteringChannel.read(byteBuffers, 0, bufferCount));
705             } else {
706                 for (int i = 0; i < bufferCount; i++) {
707                     int bytesRead = channel.read(byteBuffers[i]);
708                     if (bytesRead == -1) {
709                         if (i == 0) {
710                             return -1; // If we're end-of-stream on the first read, immediately return -1.
711                         }
712                         break;
713                     }
714                     totalBytesRead = addExact(totalBytesRead, bytesRead);
715                 }
716             }
717         } finally {
718             if (totalBytesRead > 0) { // Don't skipWritable if total is 0 or -1
719                 skipWritableBytes(totalBytesRead);
720             }
721         }
722         return totalBytesRead;
723     }
724 
725     private static int countAndPrepareBuffersForChannelIO(int byteLength, ByteBuffer[] byteBuffers) {
726         int bufferCount = 0;
727         int byteSum = 0;
728         for (ByteBuffer buffer : byteBuffers) {
729             byteSum += buffer.remaining();
730             bufferCount++;
731             if (byteSum >= byteLength) {
732                 int diff = byteSum - byteLength;
733                 if (diff > 0) {
734                     buffer.limit(buffer.limit() - diff);
735                 }
736                 break;
737             }
738         }
739         return bufferCount;
740     }
741 
742     @Override
743     public int bytesBefore(byte needle) {
744         if (!isAccessible()) {
745             throw bufferIsClosed(this);
746         }
747         final int length = readableBytes();
748         for (int i = searchOffsets(readerOffset()), skip = 0; skip < length; i++) {
749             Buffer buf = bufs[i];
750             int found = buf.bytesBefore(needle);
751             if (found != -1) {
752                 return skip + found;
753             }
754             skip += buf.readableBytes();
755         }
756         return -1;
757     }
758 
759     @Override
760     public int bytesBefore(Buffer needle) {
761         return Statics.bytesBefore(this, null, needle, null);
762     }
763 
764     @Override
765     public ByteCursor openCursor() {
766         return openCursor(readerOffset(), readableBytes());
767     }
768 
769     @Override
770     public ByteCursor openCursor(int fromOffset, int length) {
771         if (fromOffset < 0) {
772             throw new IndexOutOfBoundsException("The fromOffset cannot be negative: " + fromOffset + '.');
773         }
774         checkLength(length);
775         if (capacity < addExact(fromOffset, length)) {
776             throw new IndexOutOfBoundsException("The fromOffset+length is beyond the end of the buffer: " +
777                                                "fromOffset=" + fromOffset + ", length=" + length + '.');
778         }
779         if (closed) {
780             throw bufferIsClosed(this);
781         }
782         int startBufferIndex = searchOffsets(fromOffset);
783         int off = fromOffset - offsets[startBufferIndex];
784         Buffer startBuf = bufs[startBufferIndex];
785         ByteCursor startCursor = startBuf.openCursor(off, Math.min(startBuf.capacity() - off, length));
786         return new ForwardCompositeByteCursor(bufs, fromOffset, length, startBufferIndex, startCursor);
787     }
788 
789     @Override
790     public ByteCursor openReverseCursor(int fromOffset, int length) {
791         if (fromOffset < 0) {
792             throw new IndexOutOfBoundsException("The fromOffset cannot be negative: " + fromOffset + '.');
793         }
794         checkLength(length);
795         if (fromOffset - length < -1) {
796             throw new IndexOutOfBoundsException("The fromOffset-length would underflow the buffer: " +
797                                                "fromOffset=" + fromOffset + ", length=" + length + '.');
798         }
799         if (closed) {
800             throw bufferIsClosed(this);
801         }
802         int startBufferIndex = searchOffsets(fromOffset);
803         int off = fromOffset - offsets[startBufferIndex];
804         Buffer startBuf = bufs[startBufferIndex];
805         ByteCursor startCursor = startBuf.openReverseCursor(off, Math.min(off + 1, length));
806         return new ReverseCompositeByteCursor(bufs, fromOffset, length, startBufferIndex, startCursor);
807     }
808 
809     @Override
810     public CompositeBuffer ensureWritable(int size, int minimumGrowth, boolean allowCompaction) {
811         if (!isAccessible()) {
812             throw bufferIsClosed(this);
813         }
814         if (!isOwned()) {
815             throw new IllegalStateException("Buffer is not owned. Only owned buffers can call ensureWritable.");
816         }
817         if (size < 0) {
818             throw new IllegalArgumentException("Cannot ensure writable for a negative size: " + size + '.');
819         }
820         if (minimumGrowth < 0) {
821             throw new IllegalArgumentException("The minimum growth cannot be negative: " + minimumGrowth + '.');
822         }
823         if (readOnly) {
824             throw bufferIsReadOnly(this);
825         }
826         if (writableBytes() >= size) {
827             // We already have enough space.
828             return this;
829         }
830 
831         if (allowCompaction && size <= roff) {
832             // Let's see if we can solve some or all of the requested size with compaction.
833             // We always compact as much as is possible, regardless of size. This amortizes our work.
834             int compactableBuffers = 0;
835             for (Buffer buf : bufs) {
836                 if (buf.capacity() != buf.readerOffset()) {
837                     break;
838                 }
839                 compactableBuffers++;
840             }
841             if (compactableBuffers > 0) {
842                 Buffer[] compactable;
843                 if (compactableBuffers < bufs.length) {
844                     compactable = new Buffer[compactableBuffers];
845                     System.arraycopy(bufs, 0, compactable, 0, compactable.length);
846                     System.arraycopy(bufs, compactable.length, bufs, 0, bufs.length - compactable.length);
847                     System.arraycopy(compactable, 0, bufs, bufs.length - compactable.length, compactable.length);
848                 } else {
849                     compactable = bufs;
850                 }
851                 for (Buffer buf : compactable) {
852                     buf.resetOffsets();
853                 }
854                 computeBufferOffsets();
855                 if (writableBytes() >= size) {
856                     // Now we have enough space.
857                     return this;
858                 }
859             } else if (bufs.length == 1) {
860                 // If we only have a single component buffer, then we can safely compact that in-place.
861                 bufs[0].compact();
862                 computeBufferOffsets();
863                 if (writableBytes() >= size) {
864                     // Now we have enough space.
865                     return this;
866                 }
867             }
868         }
869 
870         int growth = Math.max(size - writableBytes(), minimumGrowth);
871         Statics.assertValidBufferSize(capacity() + (long) growth);
872         Buffer extension = allocator.allocate(growth);
873         unsafeExtendWith(extension);
874         return this;
875     }
876 
877     @Override
878     public CompositeBuffer extendWith(Send<Buffer> extension) {
879         Buffer buffer = Objects.requireNonNull(extension, "Extension buffer cannot be null.").receive();
880         if (!isAccessible() || !isOwned()) {
881             buffer.close();
882             if (!isAccessible()) {
883                 throw bufferIsClosed(this);
884             }
885             throw new IllegalStateException("This buffer cannot be extended because it is not in an owned state.");
886         }
887         if (bufs.length > 0 && buffer.readOnly() != readOnly()) {
888             buffer.close();
889             throw new IllegalArgumentException(
890                     "This buffer is " + (readOnly? "read-only" : "writable") + ", " +
891                     "and cannot be extended with a buffer that is " +
892                     (buffer.readOnly()? "read-only." : "writable."));
893         }
894 
895         long extensionCapacity = buffer.capacity();
896         if (extensionCapacity == 0) {
897             // Extending by a zero-sized buffer makes no difference. Especially since it's not allowed to change the
898             // capacity of buffers that are constituents of composite buffers.
899             // This also ensures that methods like countComponents, and forEachReadable, do not have to worry about
900             // overflow in their component counters.
901             buffer.close();
902             return this;
903         }
904 
905         long newSize = capacity() + extensionCapacity;
906         Statics.assertValidBufferSize(newSize);
907 
908         Buffer[] restoreTemp = bufs; // We need this to restore our buffer array, in case offset computations fail.
909         try {
910             bufs = filterExternalBufs(new ConcatIterable<>(Arrays.asList(bufs), List.of(buffer)));
911             computeBufferOffsets();
912             if (restoreTemp.length == 0) {
913                 readOnly = buffer.readOnly();
914             }
915         } catch (Exception e) {
916             bufs = restoreTemp;
917             throw e;
918         }
919         return this;
920     }
921 
922     private void unsafeExtendWith(Buffer extension) {
923         bufs = Arrays.copyOf(bufs, bufs.length + 1);
924         bufs[bufs.length - 1] = extension;
925         computeBufferOffsets();
926     }
927 
928     private void checkSplit(int splitOffset) {
929         if (splitOffset < 0) {
930             throw new IllegalArgumentException("The split offset cannot be negative: " + splitOffset + '.');
931         }
932         if (capacity() < splitOffset) {
933             throw new IllegalArgumentException("The split offset cannot be greater than the buffer capacity, " +
934                     "but the split offset was " + splitOffset + ", and capacity is " + capacity() + '.');
935         }
936         if (!isAccessible()) {
937             throw attachTrace(bufferIsClosed(this));
938         }
939         if (!isOwned()) {
940             throw new IllegalStateException("Cannot split a buffer that is not owned.");
941         }
942     }
943 
944     @Override
945     public CompositeBuffer split() {
946         return split(writerOffset());
947     }
948 
949     @Override
950     public CompositeBuffer split(int splitOffset) {
951         checkSplit(splitOffset);
952         if (bufs.length == 0) {
953             // Splitting a zero-length buffer is trivial.
954             return new DefaultCompositeBuffer(allocator, bufs, unsafeGetDrop());
955         }
956 
957         int i = searchOffsets(splitOffset);
958         int off = splitOffset - offsets[i];
959         Buffer[] splits = Arrays.copyOf(bufs, off == 0? i : 1 + i);
960         bufs = Arrays.copyOfRange(bufs, off == bufs[i].capacity()? 1 + i : i, bufs.length);
961         if (off > 0 && splits.length > 0 && off < splits[splits.length - 1].capacity()) {
962             splits[splits.length - 1] = bufs[0].split(off);
963         }
964         computeBufferOffsets();
965         return buildSplitBuffer(splits);
966     }
967 
968     private CompositeBuffer buildSplitBuffer(Buffer[] splits) {
969         // TODO do we need to preserve read-only state of empty buffer?
970         return new DefaultCompositeBuffer(allocator, splits, unsafeGetDrop());
971     }
972 
973     @Override
974     public CompositeBuffer splitComponentsFloor(int splitOffset) {
975         checkSplit(splitOffset);
976         if (bufs.length == 0) {
977             // Splitting a zero-length buffer is trivial.
978             return new DefaultCompositeBuffer(allocator, bufs, unsafeGetDrop());
979         }
980 
981         int i = searchOffsets(splitOffset);
982         int off = splitOffset - offsets[i];
983         if (off == bufs[i].capacity()) {
984             i++;
985         }
986         Buffer[] splits = Arrays.copyOf(bufs, i);
987         bufs = Arrays.copyOfRange(bufs, i, bufs.length);
988         computeBufferOffsets();
989         return buildSplitBuffer(splits);
990     }
991 
992     @Override
993     public CompositeBuffer splitComponentsCeil(int splitOffset) {
994         checkSplit(splitOffset);
995         if (bufs.length == 0) {
996             // Splitting a zero-length buffer is trivial.
997             return new DefaultCompositeBuffer(allocator, bufs, unsafeGetDrop());
998         }
999 
1000         int i = searchOffsets(splitOffset);
1001         int off = splitOffset - offsets[i];
1002         if (0 < off && off <= bufs[i].capacity()) {
1003             i++;
1004         }
1005         Buffer[] splits = Arrays.copyOf(bufs, i);
1006         bufs = Arrays.copyOfRange(bufs, i, bufs.length);
1007         computeBufferOffsets();
1008         return buildSplitBuffer(splits);
1009     }
1010 
1011     @Override
1012     public Buffer[] decomposeBuffer() {
1013         Buffer[] result = bufs;
1014         bufs = EMPTY_BUFFER_ARRAY;
1015         try {
1016             close();
1017         } catch (Throwable e) {
1018             for (Buffer buffer : result) {
1019                 try {
1020                     buffer.close();
1021                 } catch (Throwable ex) {
1022                     e.addSuppressed(ex);
1023                 }
1024             }
1025             throw e;
1026         }
1027         return result;
1028     }
1029 
1030     @Override
1031     public CompositeBuffer compact() {
1032         if (!isAccessible()) {
1033             throw attachTrace(bufferIsClosed(this));
1034         }
1035         if (!isOwned()) {
1036             throw attachTrace(new IllegalStateException("Buffer must be owned in order to compact."));
1037         }
1038         if (readOnly()) {
1039             throw new BufferReadOnlyException("Buffer must be writable in order to compact, but was read-only.");
1040         }
1041         int distance = roff;
1042         if (distance == 0) {
1043             return this;
1044         }
1045         int pos = 0;
1046         // TODO maybe we can delegate to a copyInto method, once it's more optimised
1047         var cursor = openCursor();
1048         while (cursor.readByte()) {
1049             setByte(pos, cursor.getByte());
1050             pos++;
1051         }
1052         readerOffset(0);
1053         writerOffset(woff - distance);
1054         return this;
1055     }
1056 
1057     @Override
1058     public int countComponents() {
1059         int sum = 0;
1060         for (Buffer buf : bufs) {
1061             sum += buf.countComponents();
1062         }
1063         return sum;
1064     }
1065 
1066     @Override
1067     public int countReadableComponents() {
1068         int sum = 0;
1069         for (Buffer buf : bufs) {
1070             sum += buf.countReadableComponents();
1071         }
1072         return sum;
1073     }
1074 
1075     @Override
1076     public int countWritableComponents() {
1077         int sum = 0;
1078         for (Buffer buf : bufs) {
1079             sum += buf.countWritableComponents();
1080         }
1081         return sum;
1082     }
1083 
1084     @Override
1085     public <E extends Exception> int forEachReadable(int initialIndex, ReadableComponentProcessor<E> processor)
1086             throws E {
1087         if (!isAccessible()) {
1088             throw attachTrace(bufferIsClosed(this));
1089         }
1090         int readableBytes = readableBytes();
1091         if (readableBytes == 0) {
1092             return 0;
1093         }
1094         checkReadBounds(readerOffset(), readableBytes);
1095         int visited = 0;
1096         for (Buffer buf : bufs) {
1097             if (buf.readableBytes() > 0) {
1098                 int roffBefore = buf.readerOffset();
1099                 int count = buf.forEachReadable(visited + initialIndex, processor);
1100                 int roffAfter = buf.readerOffset();
1101                 if (roffAfter != roffBefore) { // Check if ReadableComponent.skipReadable was called.
1102                     buf.readerOffset(roffBefore); // Reset component offset.
1103                     skipReadableBytes(roffAfter - roffBefore); // Then move *composite* buffer offset.
1104                 }
1105                 if (count > 0) {
1106                     visited += count;
1107                 } else {
1108                     visited = -visited + count;
1109                     break;
1110                 }
1111             }
1112         }
1113         return visited;
1114     }
1115 
1116     @Override
1117     public <T extends ReadableComponent & Next> ComponentIterator<T> forEachReadable() {
1118         return new CompositeComponentIterator<>((DefaultCompositeBuffer) acquire(), Buffer::forEachReadable);
1119     }
1120 
1121     @Override
1122     public <E extends Exception> int forEachWritable(int initialIndex, WritableComponentProcessor<E> processor)
1123             throws E {
1124         if (!isAccessible()) {
1125             throw attachTrace(bufferIsClosed(this));
1126         }
1127         int writableBytes = writableBytes();
1128         if (writableBytes == 0) {
1129             return 0;
1130         }
1131         checkWriteBounds(writerOffset(), writableBytes);
1132         int visited = 0;
1133         for (Buffer buf : bufs) {
1134             if (buf.writableBytes() > 0) {
1135                 int woffBefore = buf.writerOffset();
1136                 int count = buf.forEachWritable(visited + initialIndex, processor);
1137                 int woffAfter = buf.writerOffset();
1138                 if (woffAfter != woffBefore) { // Check if WritableComponent.skipWritable was called.
1139                     buf.writerOffset(woffBefore);
1140                     skipWritableBytes(woffAfter - woffBefore);
1141                 }
1142                 if (count > 0) {
1143                     visited += count;
1144                 } else {
1145                     visited = -visited + count;
1146                     break;
1147                 }
1148             }
1149         }
1150         return visited;
1151     }
1152 
1153     @Override
1154     public <T extends WritableComponent & Next> ComponentIterator<T> forEachWritable() {
1155         checkWriteBounds(writerOffset(), writableBytes());
1156         return new CompositeComponentIterator<>((DefaultCompositeBuffer) acquire(), Buffer::forEachWritable);
1157     }
1158 
1159     // <editor-fold defaultstate="collapsed" desc="Primitive accessors.">
1160     @Override
1161     public byte readByte() {
1162         return prepRead(Byte.BYTES).readByte();
1163     }
1164 
1165     @Override
1166     public byte getByte(int roff) {
1167         return prepGet(roff, Byte.BYTES).getByte(subOffset);
1168     }
1169 
1170     @Override
1171     public int readUnsignedByte() {
1172         return prepRead(Byte.BYTES).readUnsignedByte();
1173     }
1174 
1175     @Override
1176     public int getUnsignedByte(int roff) {
1177         return prepGet(roff, Byte.BYTES).getUnsignedByte(subOffset);
1178     }
1179 
1180     @Override
1181     public CompositeBuffer writeByte(byte value) {
1182         prepWrite(Byte.BYTES).writeByte(value);
1183         return this;
1184     }
1185 
1186     @Override
1187     public CompositeBuffer setByte(int woff, byte value) {
1188         prepWrite(woff, Byte.BYTES).setByte(subOffset, value);
1189         return this;
1190     }
1191 
1192     @Override
1193     public CompositeBuffer writeUnsignedByte(int value) {
1194         prepWrite(Byte.BYTES).writeUnsignedByte(value);
1195         return this;
1196     }
1197 
1198     @Override
1199     public CompositeBuffer setUnsignedByte(int woff, int value) {
1200         prepWrite(woff, Byte.BYTES).setUnsignedByte(subOffset, value);
1201         return this;
1202     }
1203 
1204     @Override
1205     public char readChar() {
1206         return prepRead(Character.BYTES).readChar();
1207     }
1208 
1209     @Override
1210     public char getChar(int roff) {
1211         return prepGet(roff, Character.BYTES).getChar(subOffset);
1212     }
1213 
1214     @Override
1215     public CompositeBuffer writeChar(char value) {
1216         prepWrite(Character.BYTES).writeChar(value);
1217         return this;
1218     }
1219 
1220     @Override
1221     public CompositeBuffer setChar(int woff, char value) {
1222         prepWrite(woff, Character.BYTES).setChar(subOffset, value);
1223         return this;
1224     }
1225 
1226     @Override
1227     public short readShort() {
1228         return prepRead(Short.BYTES).readShort();
1229     }
1230 
1231     @Override
1232     public short getShort(int roff) {
1233         return prepGet(roff, Short.BYTES).getShort(subOffset);
1234     }
1235 
1236     @Override
1237     public int readUnsignedShort() {
1238         return prepRead(Short.BYTES).readUnsignedShort();
1239     }
1240 
1241     @Override
1242     public int getUnsignedShort(int roff) {
1243         return prepGet(roff, Short.BYTES).getUnsignedShort(subOffset);
1244     }
1245 
1246     @Override
1247     public CompositeBuffer writeShort(short value) {
1248         prepWrite(Short.BYTES).writeShort(value);
1249         return this;
1250     }
1251 
1252     @Override
1253     public CompositeBuffer setShort(int woff, short value) {
1254         prepWrite(woff, Short.BYTES).setShort(subOffset, value);
1255         return this;
1256     }
1257 
1258     @Override
1259     public CompositeBuffer writeUnsignedShort(int value) {
1260         prepWrite(Short.BYTES).writeUnsignedShort(value);
1261         return this;
1262     }
1263 
1264     @Override
1265     public CompositeBuffer setUnsignedShort(int woff, int value) {
1266         prepWrite(woff, Short.BYTES).setUnsignedShort(subOffset, value);
1267         return this;
1268     }
1269 
1270     @Override
1271     public int readMedium() {
1272         return prepRead(3).readMedium();
1273     }
1274 
1275     @Override
1276     public int getMedium(int roff) {
1277         return prepGet(roff, 3).getMedium(subOffset);
1278     }
1279 
1280     @Override
1281     public int readUnsignedMedium() {
1282         return prepRead(3).readUnsignedMedium();
1283     }
1284 
1285     @Override
1286     public int getUnsignedMedium(int roff) {
1287         return prepGet(roff, 3).getUnsignedMedium(subOffset);
1288     }
1289 
1290     @Override
1291     public CompositeBuffer writeMedium(int value) {
1292         prepWrite(3).writeMedium(value);
1293         return this;
1294     }
1295 
1296     @Override
1297     public CompositeBuffer setMedium(int woff, int value) {
1298         prepWrite(woff, 3).setMedium(subOffset, value);
1299         return this;
1300     }
1301 
1302     @Override
1303     public CompositeBuffer writeUnsignedMedium(int value) {
1304         prepWrite(3).writeUnsignedMedium(value);
1305         return this;
1306     }
1307 
1308     @Override
1309     public CompositeBuffer setUnsignedMedium(int woff, int value) {
1310         prepWrite(woff, 3).setUnsignedMedium(subOffset, value);
1311         return this;
1312     }
1313 
1314     @Override
1315     public int readInt() {
1316         return prepRead(Integer.BYTES).readInt();
1317     }
1318 
1319     @Override
1320     public int getInt(int roff) {
1321         return prepGet(roff, Integer.BYTES).getInt(subOffset);
1322     }
1323 
1324     @Override
1325     public long readUnsignedInt() {
1326         return prepRead(Integer.BYTES).readUnsignedInt();
1327     }
1328 
1329     @Override
1330     public long getUnsignedInt(int roff) {
1331         return prepGet(roff, Integer.BYTES).getUnsignedInt(subOffset);
1332     }
1333 
1334     @Override
1335     public CompositeBuffer writeInt(int value) {
1336         prepWrite(Integer.BYTES).writeInt(value);
1337         return this;
1338     }
1339 
1340     @Override
1341     public CompositeBuffer setInt(int woff, int value) {
1342         prepWrite(woff, Integer.BYTES).setInt(subOffset, value);
1343         return this;
1344     }
1345 
1346     @Override
1347     public CompositeBuffer writeUnsignedInt(long value) {
1348         prepWrite(Integer.BYTES).writeUnsignedInt(value);
1349         return this;
1350     }
1351 
1352     @Override
1353     public CompositeBuffer setUnsignedInt(int woff, long value) {
1354         prepWrite(woff, Integer.BYTES).setUnsignedInt(subOffset, value);
1355         return this;
1356     }
1357 
1358     @Override
1359     public float readFloat() {
1360         return prepRead(Float.BYTES).readFloat();
1361     }
1362 
1363     @Override
1364     public float getFloat(int roff) {
1365         return prepGet(roff, Float.BYTES).getFloat(subOffset);
1366     }
1367 
1368     @Override
1369     public CompositeBuffer writeFloat(float value) {
1370         prepWrite(Float.BYTES).writeFloat(value);
1371         return this;
1372     }
1373 
1374     @Override
1375     public CompositeBuffer setFloat(int woff, float value) {
1376         prepWrite(woff, Float.BYTES).setFloat(subOffset, value);
1377         return this;
1378     }
1379 
1380     @Override
1381     public long readLong() {
1382         return prepRead(Long.BYTES).readLong();
1383     }
1384 
1385     @Override
1386     public long getLong(int roff) {
1387         return prepGet(roff, Long.BYTES).getLong(subOffset);
1388     }
1389 
1390     @Override
1391     public CompositeBuffer writeLong(long value) {
1392         prepWrite(Long.BYTES).writeLong(value);
1393         return this;
1394     }
1395 
1396     @Override
1397     public CompositeBuffer setLong(int woff, long value) {
1398         prepWrite(woff, Long.BYTES).setLong(subOffset, value);
1399         return this;
1400     }
1401 
1402     @Override
1403     public double readDouble() {
1404         return prepRead(Double.BYTES).readDouble();
1405     }
1406 
1407     @Override
1408     public double getDouble(int roff) {
1409         return prepGet(roff, Double.BYTES).getDouble(subOffset);
1410     }
1411 
1412     @Override
1413     public CompositeBuffer writeDouble(double value) {
1414         prepWrite(Double.BYTES).writeDouble(value);
1415         return this;
1416     }
1417 
1418     @Override
1419     public CompositeBuffer setDouble(int woff, double value) {
1420         prepWrite(woff, Double.BYTES).setDouble(subOffset, value);
1421         return this;
1422     }
1423     // </editor-fold>
1424 
1425     @Override
1426     protected Owned<DefaultCompositeBuffer> prepareSend() {
1427         @SuppressWarnings("unchecked")
1428         Send<Buffer>[] sends = new Send[bufs.length];
1429         try {
1430             for (int i = 0; i < bufs.length; i++) {
1431                 sends[i] = bufs[i].send();
1432             }
1433         } catch (Throwable throwable) {
1434             // Repair our bufs array.
1435             for (int i = 0; i < sends.length; i++) {
1436                 if (sends[i] != null) {
1437                     try {
1438                         bufs[i] = sends[i].receive();
1439                     } catch (Exception e) {
1440                         throwable.addSuppressed(e);
1441                     }
1442                 }
1443             }
1444             throw throwable;
1445         }
1446         boolean readOnly = this.readOnly;
1447         int implicitCapacityLimit = this.implicitCapacityLimit;
1448         return drop -> {
1449             Buffer[] received = new Buffer[sends.length];
1450             for (int i = 0; i < sends.length; i++) {
1451                 received[i] = sends[i].receive();
1452             }
1453             var composite = new DefaultCompositeBuffer(allocator, received, drop);
1454             composite.readOnly = readOnly;
1455             composite.implicitCapacityLimit = implicitCapacityLimit;
1456             drop.attach(composite);
1457             return composite;
1458         };
1459     }
1460 
1461     @Override
1462     protected void makeInaccessible() {
1463         capacity = 0;
1464         roff = 0;
1465         woff = 0;
1466         readOnly = false;
1467         closed = true;
1468     }
1469 
1470     @Override
1471     protected boolean isOwned() {
1472         return super.isOwned() && allConstituentsAreOwned();
1473     }
1474 
1475     @Override
1476     public CompositeBuffer touch(Object hint) {
1477         super.touch(hint);
1478         for (Buffer buf : bufs) {
1479             buf.touch(hint);
1480         }
1481         return this;
1482     }
1483 
1484     private boolean allConstituentsAreOwned() {
1485         boolean result = true;
1486         for (Buffer buf : bufs) {
1487             result &= Statics.isOwned((ResourceSupport<?, ?>) buf);
1488         }
1489         return result;
1490     }
1491 
1492     long readPassThrough() {
1493         var buf = choosePassThroughBuffer(subOffset++);
1494         assert buf != tornBufAccessors: "Recursive call to torn buffer.";
1495         return buf.readUnsignedByte();
1496     }
1497 
1498     void writePassThrough(int value) {
1499         var buf = choosePassThroughBuffer(subOffset++);
1500         assert buf != tornBufAccessors: "Recursive call to torn buffer.";
1501         buf.writeUnsignedByte(value);
1502     }
1503 
1504     long getPassThrough(int roff) {
1505         var buf = chooseBuffer(roff, 1);
1506         assert buf != tornBufAccessors: "Recursive call to torn buffer.";
1507         return buf.getUnsignedByte(subOffset);
1508     }
1509 
1510     void setPassThrough(int woff, int value) {
1511         var buf = chooseBuffer(woff, 1);
1512         assert buf != tornBufAccessors: "Recursive call to torn buffer.";
1513         buf.setUnsignedByte(subOffset, value);
1514     }
1515 
1516     private BufferAccessor prepRead(int size) {
1517         var buf = prepRead(roff, size);
1518         roff += size;
1519         return buf;
1520     }
1521 
1522     private BufferAccessor prepRead(int index, int size) {
1523         checkReadBounds(index, size);
1524         return chooseBuffer(index, size);
1525     }
1526 
1527     private void checkReadBounds(int index, int size) {
1528         if (index < 0 || woff < index + size) {
1529             throw indexOutOfBounds(index, false);
1530         }
1531     }
1532 
1533     private BufferAccessor prepGet(int index, int size) {
1534         checkGetBounds(index, size);
1535         return chooseBuffer(index, size);
1536     }
1537 
1538     private void checkGetBounds(int index, int size) {
1539         if (index < 0 || capacity < index + size) {
1540             throw indexOutOfBounds(index, false);
1541         }
1542     }
1543 
1544     private BufferAccessor prepWrite(int size) {
1545         if (writableBytes() < size && woff + size <= implicitCapacityLimit && isOwned()) {
1546             final int minGrowth;
1547             if (bufs.length == 0) {
1548                 minGrowth = Math.min(implicitCapacityLimit, FIRST_AUTOMATIC_COMPONENT_SIZE);
1549             } else {
1550                 minGrowth = Math.min(
1551                         Math.max(roundToPowerOfTwo(capacity() / bufs.length), size),
1552                         implicitCapacityLimit - capacity);
1553             }
1554             ensureWritable(size, minGrowth, false);
1555         }
1556         var buf = prepWrite(woff, size);
1557         woff += size;
1558         return buf;
1559     }
1560 
1561     private BufferAccessor prepWrite(int index, int size) {
1562         checkWriteBounds(index, size);
1563         return chooseBuffer(index, size);
1564     }
1565 
1566     private void checkWriteBounds(int index, int size) {
1567         if (index < 0 || capacity < index + size || readOnly) {
1568             throw indexOutOfBounds(index, true);
1569         }
1570     }
1571 
1572     private RuntimeException indexOutOfBounds(int index, boolean write) {
1573         if (closed) {
1574             return bufferIsClosed(this);
1575         }
1576         if (write && readOnly) {
1577             return bufferIsReadOnly(this);
1578         }
1579         return new IndexOutOfBoundsException(
1580                 "Index " + index + " is out of bounds: [read 0 to " + woff + ", write 0 to " +
1581                 capacity + "].");
1582     }
1583 
1584     private BufferAccessor chooseBuffer(int index, int size) {
1585         int i = searchOffsets(index);
1586         if (i == bufs.length) {
1587             // This happens when the read/write offsets are parked 1 byte beyond the end of the buffer.
1588             // In that case it should not matter what buffer is returned, because it shouldn't be used anyway.
1589             return null;
1590         }
1591         int off = index - offsets[i];
1592         Buffer candidate = bufs[i];
1593         if (off + size <= candidate.capacity()) {
1594             subOffset = off;
1595             return candidate;
1596         }
1597         subOffset = index;
1598         return tornBufAccessors;
1599     }
1600 
1601     private BufferAccessor choosePassThroughBuffer(int index) {
1602         int i = searchOffsets(index);
1603         return bufs[i];
1604     }
1605 
1606     private int searchOffsets(int index) {
1607         int i = Arrays.binarySearch(offsets, index);
1608         return i < 0? -(i + 2) : i;
1609     }
1610 
1611     @Override
1612     public boolean equals(Object o) {
1613         return o instanceof Buffer && Statics.equals(this, (Buffer) o);
1614     }
1615 
1616     @Override
1617     public int hashCode() {
1618         return Statics.hashCode(this);
1619     }
1620 
1621     // <editor-fold defaultstate="collapsed" desc="Torn buffer access.">
1622     private static final class TornBufferAccessor implements BufferAccessor {
1623         private final DefaultCompositeBuffer buf;
1624 
1625         private TornBufferAccessor(DefaultCompositeBuffer buf) {
1626             this.buf = buf;
1627         }
1628 
1629         @Override
1630         public byte readByte() {
1631             throw new AssertionError("Method should not be used.");
1632         }
1633 
1634         @Override
1635         public byte getByte(int roff) {
1636             throw new AssertionError("Method should not be used.");
1637         }
1638 
1639         @Override
1640         public int readUnsignedByte() {
1641             throw new AssertionError("Method should not be used.");
1642         }
1643 
1644         @Override
1645         public int getUnsignedByte(int roff) {
1646             throw new AssertionError("Method should not be used.");
1647         }
1648 
1649         @Override
1650         public Buffer writeByte(byte value) {
1651             throw new AssertionError("Method should not be used.");
1652         }
1653 
1654         @Override
1655         public Buffer setByte(int woff, byte value) {
1656             throw new AssertionError("Method should not be used.");
1657         }
1658 
1659         @Override
1660         public Buffer writeUnsignedByte(int value) {
1661             throw new AssertionError("Method should not be used.");
1662         }
1663 
1664         @Override
1665         public Buffer setUnsignedByte(int woff, int value) {
1666             throw new AssertionError("Method should not be used.");
1667         }
1668 
1669         @Override
1670         public char readChar() {
1671             return (char) (read() << 8 | read());
1672         }
1673 
1674         @Override
1675         public char getChar(int roff) {
1676             return (char) (read(roff) << 8 | read(roff + 1));
1677         }
1678 
1679         @Override
1680         public Buffer writeChar(char value) {
1681             write(value >>> 8);
1682             write(value & 0xFF);
1683             return buf;
1684         }
1685 
1686         @Override
1687         public Buffer setChar(int woff, char value) {
1688             write(woff, value >>> 8);
1689             write(woff + 1, value & 0xFF);
1690             return buf;
1691         }
1692 
1693         @Override
1694         public short readShort() {
1695             return (short) (read() << 8 | read());
1696         }
1697 
1698         @Override
1699         public short getShort(int roff) {
1700             return (short) (read(roff) << 8 | read(roff + 1));
1701         }
1702 
1703         @Override
1704         public int readUnsignedShort() {
1705             return (int) (read() << 8 | read()) & 0xFFFF;
1706         }
1707 
1708         @Override
1709         public int getUnsignedShort(int roff) {
1710             return (int) (read(roff) << 8 | read(roff + 1)) & 0xFFFF;
1711         }
1712 
1713         @Override
1714         public Buffer writeShort(short value) {
1715             write(value >>> 8);
1716             write(value & 0xFF);
1717             return buf;
1718         }
1719 
1720         @Override
1721         public Buffer setShort(int woff, short value) {
1722             write(woff, value >>> 8);
1723             write(woff + 1, value & 0xFF);
1724             return buf;
1725         }
1726 
1727         @Override
1728         public Buffer writeUnsignedShort(int value) {
1729             write(value >>> 8);
1730             write(value & 0xFF);
1731             return buf;
1732         }
1733 
1734         @Override
1735         public Buffer setUnsignedShort(int woff, int value) {
1736             write(woff, value >>> 8);
1737             write(woff + 1, value & 0xFF);
1738             return buf;
1739         }
1740 
1741         @Override
1742         public int readMedium() {
1743             return (int) (read() << 16 | read() << 8 | read());
1744         }
1745 
1746         @Override
1747         public int getMedium(int roff) {
1748             return (int) (read(roff) << 16 | read(roff + 1) << 8 | read(roff + 2));
1749         }
1750 
1751         @Override
1752         public int readUnsignedMedium() {
1753             return (int) (read() << 16 | read() << 8 | read()) & 0xFFFFFF;
1754         }
1755 
1756         @Override
1757         public int getUnsignedMedium(int roff) {
1758             return (int) (read(roff) << 16 | read(roff + 1) << 8 | read(roff + 2)) & 0xFFFFFF;
1759         }
1760 
1761         @Override
1762         public Buffer writeMedium(int value) {
1763             write(value >>> 16);
1764             write(value >>> 8 & 0xFF);
1765             write(value & 0xFF);
1766             return buf;
1767         }
1768 
1769         @Override
1770         public Buffer setMedium(int woff, int value) {
1771             write(woff, value >>> 16);
1772             write(woff + 1, value >>> 8 & 0xFF);
1773             write(woff + 2, value & 0xFF);
1774             return buf;
1775         }
1776 
1777         @Override
1778         public Buffer writeUnsignedMedium(int value) {
1779             write(value >>> 16);
1780             write(value >>> 8 & 0xFF);
1781             write(value & 0xFF);
1782             return buf;
1783         }
1784 
1785         @Override
1786         public Buffer setUnsignedMedium(int woff, int value) {
1787             write(woff, value >>> 16);
1788             write(woff + 1, value >>> 8 & 0xFF);
1789             write(woff + 2, value & 0xFF);
1790             return buf;
1791         }
1792 
1793         @Override
1794         public int readInt() {
1795             return (int) (read() << 24 | read() << 16 | read() << 8 | read());
1796         }
1797 
1798         @Override
1799         public int getInt(int roff) {
1800             return (int) (read(roff) << 24 | read(roff + 1) << 16 | read(roff + 2) << 8 | read(roff + 3));
1801         }
1802 
1803         @Override
1804         public long readUnsignedInt() {
1805             return (read() << 24 | read() << 16 | read() << 8 | read()) & 0xFFFFFFFFL;
1806         }
1807 
1808         @Override
1809         public long getUnsignedInt(int roff) {
1810             return (read(roff) << 24 | read(roff + 1) << 16 | read(roff + 2) << 8 | read(roff + 3)) & 0xFFFFFFFFL;
1811         }
1812 
1813         @Override
1814         public Buffer writeInt(int value) {
1815             write(value >>> 24);
1816             write(value >>> 16 & 0xFF);
1817             write(value >>> 8 & 0xFF);
1818             write(value & 0xFF);
1819             return buf;
1820         }
1821 
1822         @Override
1823         public Buffer setInt(int woff, int value) {
1824             write(woff, value >>> 24);
1825             write(woff + 1, value >>> 16 & 0xFF);
1826             write(woff + 2, value >>> 8 & 0xFF);
1827             write(woff + 3, value & 0xFF);
1828             return buf;
1829         }
1830 
1831         @Override
1832         public Buffer writeUnsignedInt(long value) {
1833             write((int) (value >>> 24));
1834             write((int) (value >>> 16 & 0xFF));
1835             write((int) (value >>> 8 & 0xFF));
1836             write((int) (value & 0xFF));
1837             return buf;
1838         }
1839 
1840         @Override
1841         public Buffer setUnsignedInt(int woff, long value) {
1842             write(woff, (int) (value >>> 24));
1843             write(woff + 1, (int) (value >>> 16 & 0xFF));
1844             write(woff + 2, (int) (value >>> 8 & 0xFF));
1845             write(woff + 3, (int) (value & 0xFF));
1846             return buf;
1847         }
1848 
1849         @Override
1850         public float readFloat() {
1851             return Float.intBitsToFloat(readInt());
1852         }
1853 
1854         @Override
1855         public float getFloat(int roff) {
1856             return Float.intBitsToFloat(getInt(roff));
1857         }
1858 
1859         @Override
1860         public Buffer writeFloat(float value) {
1861             return writeUnsignedInt(Float.floatToRawIntBits(value));
1862         }
1863 
1864         @Override
1865         public Buffer setFloat(int woff, float value) {
1866             return setUnsignedInt(woff, Float.floatToRawIntBits(value));
1867         }
1868 
1869         @Override
1870         public long readLong() {
1871             return read() << 56 | read() << 48 | read() << 40 | read() << 32 |
1872                    read() << 24 | read() << 16 | read() << 8 | read();
1873         }
1874 
1875         @Override
1876         public long getLong(int roff) {
1877             return read(roff) << 56 | read(roff + 1) << 48 | read(roff + 2) << 40 | read(roff + 3) << 32 |
1878                    read(roff + 4) << 24 | read(roff + 5) << 16 | read(roff + 6) << 8 | read(roff + 7);
1879         }
1880 
1881         @Override
1882         public Buffer writeLong(long value) {
1883             write((int) (value >>> 56));
1884             write((int) (value >>> 48 & 0xFF));
1885             write((int) (value >>> 40 & 0xFF));
1886             write((int) (value >>> 32 & 0xFF));
1887             write((int) (value >>> 24 & 0xFF));
1888             write((int) (value >>> 16 & 0xFF));
1889             write((int) (value >>> 8 & 0xFF));
1890             write((int) (value & 0xFF));
1891             return buf;
1892         }
1893 
1894         @Override
1895         public Buffer setLong(int woff, long value) {
1896             write(woff, (int) (value >>> 56));
1897             write(woff + 1, (int) (value >>> 48 & 0xFF));
1898             write(woff + 2, (int) (value >>> 40 & 0xFF));
1899             write(woff + 3, (int) (value >>> 32 & 0xFF));
1900             write(woff + 4, (int) (value >>> 24 & 0xFF));
1901             write(woff + 5, (int) (value >>> 16 & 0xFF));
1902             write(woff + 6, (int) (value >>> 8 & 0xFF));
1903             write(woff + 7, (int) (value & 0xFF));
1904             return buf;
1905         }
1906 
1907         @Override
1908         public double readDouble() {
1909             return Double.longBitsToDouble(readLong());
1910         }
1911 
1912         @Override
1913         public double getDouble(int roff) {
1914             return Double.longBitsToDouble(getLong(roff));
1915         }
1916 
1917         @Override
1918         public Buffer writeDouble(double value) {
1919             return writeLong(Double.doubleToRawLongBits(value));
1920         }
1921 
1922         @Override
1923         public Buffer setDouble(int woff, double value) {
1924             return setLong(woff, Double.doubleToRawLongBits(value));
1925         }
1926 
1927         private long read() {
1928             return buf.readPassThrough();
1929         }
1930 
1931         private void write(int value) {
1932             buf.writePassThrough(value);
1933         }
1934 
1935         private long read(int roff) {
1936             return buf.getPassThrough(roff);
1937         }
1938 
1939         private void write(int woff, int value) {
1940             buf.setPassThrough(woff, value);
1941         }
1942     }
1943     // </editor-fold>
1944 
1945     private static final class ForwardCompositeByteCursor implements ByteCursor {
1946         final Buffer[] bufs;
1947         int index;
1948         final int end;
1949         int bufferIndex;
1950         int initOffset;
1951         ByteCursor cursor;
1952         byte byteValue;
1953 
1954         ForwardCompositeByteCursor(Buffer[] bufs, int fromOffset, int length, int startBufferIndex,
1955                                    ByteCursor startCursor) {
1956             this.bufs = bufs;
1957             index = fromOffset;
1958             end = fromOffset + length;
1959             bufferIndex = startBufferIndex;
1960             initOffset = startCursor.currentOffset();
1961             cursor = startCursor;
1962             byteValue = -1;
1963         }
1964 
1965         @Override
1966         public boolean readByte() {
1967             if (cursor.readByte()) {
1968                 byteValue = cursor.getByte();
1969                 return true;
1970             }
1971             if (bytesLeft() > 0) {
1972                 nextCursor();
1973                 cursor.readByte();
1974                 byteValue = cursor.getByte();
1975                 return true;
1976             }
1977             return false;
1978         }
1979 
1980         private void nextCursor() {
1981             bufferIndex++;
1982             Buffer nextBuf = bufs[bufferIndex];
1983             cursor = nextBuf.openCursor(0, Math.min(nextBuf.capacity(), bytesLeft()));
1984             initOffset = 0;
1985         }
1986 
1987         @Override
1988         public byte getByte() {
1989             return byteValue;
1990         }
1991 
1992         @Override
1993         public int currentOffset() {
1994             int currOff = cursor.currentOffset();
1995             index += currOff - initOffset;
1996             initOffset = currOff;
1997             return index;
1998         }
1999 
2000         @Override
2001         public int bytesLeft() {
2002             return end - currentOffset();
2003         }
2004     }
2005 
2006     private static final class ReverseCompositeByteCursor implements ByteCursor {
2007         final Buffer[] bufs;
2008         int index;
2009         final int end;
2010         int bufferIndex;
2011         int initOffset;
2012         ByteCursor cursor;
2013         byte byteValue;
2014 
2015         ReverseCompositeByteCursor(Buffer[] bufs, int fromOffset, int length,
2016                                    int startBufferIndex, ByteCursor startCursor) {
2017             this.bufs = bufs;
2018             index = fromOffset;
2019             end = fromOffset - length;
2020             bufferIndex = startBufferIndex;
2021             initOffset = startCursor.currentOffset();
2022             cursor = startCursor;
2023             byteValue = -1;
2024         }
2025 
2026         @Override
2027         public boolean readByte() {
2028             if (cursor.readByte()) {
2029                 byteValue = cursor.getByte();
2030                 return true;
2031             }
2032             if (bytesLeft() > 0) {
2033                 nextCursor();
2034                 cursor.readByte();
2035                 byteValue = cursor.getByte();
2036                 return true;
2037             }
2038             return false;
2039         }
2040 
2041         private void nextCursor() {
2042             bufferIndex--;
2043             Buffer nextBuf = bufs[bufferIndex];
2044             int length = Math.min(nextBuf.capacity(), bytesLeft());
2045             int offset = nextBuf.capacity() - 1;
2046             cursor = nextBuf.openReverseCursor(offset, length);
2047             initOffset = offset;
2048         }
2049 
2050         @Override
2051         public byte getByte() {
2052             return byteValue;
2053         }
2054 
2055         @Override
2056         public int currentOffset() {
2057             int currOff = cursor.currentOffset();
2058             index -= initOffset - currOff;
2059             initOffset = currOff;
2060             return index;
2061         }
2062 
2063         @Override
2064         public int bytesLeft() {
2065             return currentOffset() - end;
2066         }
2067     }
2068 
2069     private static final class ByteBufferCollector
2070             implements ReadableComponentProcessor<RuntimeException>, WritableComponentProcessor<RuntimeException> {
2071         final ByteBuffer[] buffers;
2072 
2073         private ByteBufferCollector(int expectedBufferCount) {
2074             buffers = new ByteBuffer[expectedBufferCount];
2075         }
2076 
2077         @Override
2078         public boolean process(int index, ReadableComponent component) {
2079             buffers[index] = component.readableBuffer();
2080             return true;
2081         }
2082 
2083         @Override
2084         public boolean process(int index, WritableComponent component) {
2085             buffers[index] = component.writableBuffer();
2086             return true;
2087         }
2088     }
2089 
2090     private static final class CompositeComponentIterator<T extends Next> implements ComponentIterator<T> {
2091         private final DefaultCompositeBuffer compositeBuffer;
2092         private final Function<Buffer, ComponentIterator<T>> intoIterator;
2093         NextComponent<T> readableNext;
2094 
2095         private CompositeComponentIterator(DefaultCompositeBuffer compositeBuffer,
2096                                            Function<Buffer, ComponentIterator<T>> intoIterator) {
2097             this.compositeBuffer = compositeBuffer;
2098             this.intoIterator = intoIterator;
2099         }
2100 
2101         @SuppressWarnings("unchecked")
2102         @Override
2103         public T first() {
2104             return compositeBuffer.bufs.length > 0 ?
2105                     (T) (readableNext = new NextComponent<>(compositeBuffer, intoIterator)) : null;
2106         }
2107 
2108         @Override
2109         public void close() {
2110             if (readableNext != null) {
2111                 readableNext.close();
2112             }
2113             compositeBuffer.close();
2114         }
2115     }
2116 
2117     private static final class NextComponent<T extends Next>
2118             implements ReadableComponent, WritableComponent, Next, SafeCloseable {
2119         private final DefaultCompositeBuffer compositeBuffer;
2120         private final Function<Buffer, ComponentIterator<T>> intoIterator;
2121         private final Buffer[] bufs;
2122         int nextIndex;
2123         ComponentIterator<T> currentItr;
2124         T currentComponent;
2125         int pastOffset;
2126         int currentReadSkip;
2127         int currentWriteSkip;
2128 
2129         private NextComponent(DefaultCompositeBuffer compositeBuffer,
2130                               Function<Buffer, ComponentIterator<T>> intoIterator) {
2131             this.compositeBuffer = compositeBuffer;
2132             this.intoIterator = intoIterator;
2133             bufs = compositeBuffer.bufs;
2134             nextComponent();
2135         }
2136 
2137         @SuppressWarnings("unchecked")
2138         @Override
2139         public <N extends Next> N next() {
2140             if (currentComponent == null) {
2141                 return null; // Already at the end of the iteration.
2142             }
2143             nextComponent();
2144             return currentComponent != null? (N) this : null;
2145         }
2146 
2147         private void nextComponent() {
2148             if (currentComponent != null) {
2149                 currentComponent = currentComponent.next();
2150             }
2151             while (currentComponent == null) {
2152                 if (currentItr != null) {
2153                     currentItr.close();
2154                     currentItr = null;
2155                 }
2156                 if (nextIndex >= bufs.length) {
2157                     return;
2158                 }
2159                 currentItr = intoIterator.apply(bufs[nextIndex]);
2160                 nextIndex++;
2161                 currentComponent = currentItr.first();
2162             }
2163         }
2164 
2165         @Override
2166         public boolean hasReadableArray() {
2167             return ((ReadableComponent) currentComponent).hasReadableArray();
2168         }
2169 
2170         @Override
2171         public byte[] readableArray() {
2172             return ((ReadableComponent) currentComponent).readableArray();
2173         }
2174 
2175         @Override
2176         public int readableArrayOffset() {
2177             return ((ReadableComponent) currentComponent).readableArrayOffset();
2178         }
2179 
2180         @Override
2181         public int readableArrayLength() {
2182             return ((ReadableComponent) currentComponent).readableArrayLength();
2183         }
2184 
2185         @Override
2186         public long readableNativeAddress() {
2187             return ((ReadableComponent) currentComponent).readableNativeAddress();
2188         }
2189 
2190         @Override
2191         public ByteBuffer readableBuffer() {
2192             return ((ReadableComponent) currentComponent).readableBuffer();
2193         }
2194 
2195         @Override
2196         public int readableBytes() {
2197             return ((ReadableComponent) currentComponent).readableBytes();
2198         }
2199 
2200         @Override
2201         public ByteCursor openCursor() {
2202             return ((ReadableComponent) currentComponent).openCursor();
2203         }
2204 
2205         @Override
2206         public ReadableComponent skipReadableBytes(int byteCount) {
2207             ((ReadableComponent) currentComponent).skipReadableBytes(byteCount);
2208             compositeBuffer.readerOffset(pastOffset + currentReadSkip + byteCount);
2209             currentReadSkip += byteCount; // This needs to be after the bounds-checks.
2210             return this;
2211         }
2212 
2213         @Override
2214         public boolean hasWritableArray() {
2215             return ((WritableComponent) currentComponent).hasWritableArray();
2216         }
2217 
2218         @Override
2219         public byte[] writableArray() {
2220             return ((WritableComponent) currentComponent).writableArray();
2221         }
2222 
2223         @Override
2224         public int writableArrayOffset() {
2225             return ((WritableComponent) currentComponent).writableArrayOffset();
2226         }
2227 
2228         @Override
2229         public int writableArrayLength() {
2230             return ((WritableComponent) currentComponent).writableArrayLength();
2231         }
2232 
2233         @Override
2234         public long writableNativeAddress() {
2235             return ((WritableComponent) currentComponent).writableNativeAddress();
2236         }
2237 
2238         @Override
2239         public int writableBytes() {
2240             return ((WritableComponent) currentComponent).writableBytes();
2241         }
2242 
2243         @Override
2244         public ByteBuffer writableBuffer() {
2245             return ((WritableComponent) currentComponent).writableBuffer();
2246         }
2247 
2248         @Override
2249         public WritableComponent skipWritableBytes(int byteCount) {
2250             ((WritableComponent) currentComponent).skipWritableBytes(byteCount);
2251             compositeBuffer.writerOffset(pastOffset + currentWriteSkip + byteCount);
2252             currentWriteSkip += byteCount; // This needs to be after the bounds-checks.
2253             return this;
2254         }
2255 
2256         @Override
2257         public void close() {
2258             currentComponent = null;
2259             if (currentItr != null) {
2260                 currentItr.close();
2261                 currentItr = null;
2262             }
2263         }
2264     }
2265 }