HBASE-26921 Rewrite the counting cells part in TestMultiVersions (#4316)
[hbase.git] / hbase-common / src / main / java / org / apache / hadoop / hbase / nio / MultiByteBuff.java
blobf9000ed953add05e72b198f9b84aa151dc068e3a
1 /**
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
10 * http://www.apache.org/licenses/LICENSE-2.0
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
18 package org.apache.hadoop.hbase.nio;
20 import static org.apache.hadoop.hbase.io.ByteBuffAllocator.NONE;
22 import java.io.IOException;
23 import java.nio.BufferOverflowException;
24 import java.nio.BufferUnderflowException;
25 import java.nio.ByteBuffer;
26 import java.nio.InvalidMarkException;
27 import java.nio.channels.FileChannel;
28 import java.nio.channels.ReadableByteChannel;
29 import java.util.Iterator;
30 import java.util.NoSuchElementException;
32 import org.apache.hadoop.hbase.io.ByteBuffAllocator.Recycler;
33 import org.apache.hadoop.hbase.util.ByteBufferUtils;
34 import org.apache.hadoop.hbase.util.Bytes;
35 import org.apache.hadoop.hbase.util.ObjectIntPair;
36 import org.apache.yetus.audience.InterfaceAudience;
38 /**
39 * Provides a unified view of all the underlying ByteBuffers and will look as if a bigger
40 * sequential buffer. This class provides similar APIs as in {@link ByteBuffer} to put/get int,
41 * short, long etc and doing operations like mark, reset, slice etc. This has to be used when
42 * data is split across multiple byte buffers and we don't want copy them to single buffer
43 * for reading from it.
45 @InterfaceAudience.Private
46 public class MultiByteBuff extends ByteBuff {
48 private final ByteBuffer[] items;
49 // Pointer to the current item in the MBB
50 private ByteBuffer curItem = null;
51 // Index of the current item in the MBB
52 private int curItemIndex = 0;
54 private int limit = 0;
55 private int limitedItemIndex;
56 private int markedItemIndex = -1;
57 private final int[] itemBeginPos;
59 private Iterator<ByteBuffer> buffsIterator = new Iterator<ByteBuffer>() {
60 @Override
61 public boolean hasNext() {
62 return curItemIndex < limitedItemIndex ||
63 (curItemIndex == limitedItemIndex && items[curItemIndex].hasRemaining());
66 @Override
67 public ByteBuffer next() {
68 if (curItemIndex >= items.length) {
69 throw new NoSuchElementException("items overflow");
71 curItem = items[curItemIndex++];
72 return curItem;
76 public MultiByteBuff(ByteBuffer... items) {
77 this(NONE, items);
80 public MultiByteBuff(Recycler recycler, ByteBuffer... items) {
81 this(new RefCnt(recycler), items);
84 MultiByteBuff(RefCnt refCnt, ByteBuffer... items) {
85 this.refCnt = refCnt;
86 assert items != null;
87 assert items.length > 0;
88 this.items = items;
89 this.curItem = this.items[this.curItemIndex];
90 // See below optimization in getInt(int) where we check whether the given index land in current
91 // item. For this we need to check whether the passed index is less than the next item begin
92 // offset. To handle this effectively for the last item buffer, we add an extra item into this
93 // array.
94 itemBeginPos = new int[items.length + 1];
95 int offset = 0;
96 for (int i = 0; i < items.length; i++) {
97 ByteBuffer item = items[i];
98 item.rewind();
99 itemBeginPos[i] = offset;
100 int l = item.limit() - item.position();
101 offset += l;
103 this.limit = offset;
104 this.itemBeginPos[items.length] = offset + 1;
105 this.limitedItemIndex = this.items.length - 1;
108 private MultiByteBuff(RefCnt refCnt, ByteBuffer[] items, int[] itemBeginPos, int limit,
109 int limitedIndex, int curItemIndex, int markedIndex) {
110 this.refCnt = refCnt;
111 this.items = items;
112 this.curItemIndex = curItemIndex;
113 this.curItem = this.items[this.curItemIndex];
114 this.itemBeginPos = itemBeginPos;
115 this.limit = limit;
116 this.limitedItemIndex = limitedIndex;
117 this.markedItemIndex = markedIndex;
121 * @throws UnsupportedOperationException MBB does not support
122 * array based operations
124 @Override
125 public byte[] array() {
126 throw new UnsupportedOperationException();
130 * @throws UnsupportedOperationException MBB does not
131 * support array based operations
133 @Override
134 public int arrayOffset() {
135 throw new UnsupportedOperationException();
139 * @return false. MBB does not support array based operations
141 @Override
142 public boolean hasArray() {
143 return false;
147 * @return the total capacity of this MultiByteBuffer.
149 @Override
150 public int capacity() {
151 checkRefCount();
152 int c = 0;
153 for (ByteBuffer item : this.items) {
154 c += item.capacity();
156 return c;
160 * Fetches the byte at the given index. Does not change position of the underlying ByteBuffers
161 * @param index
162 * @return the byte at the given index
164 @Override
165 public byte get(int index) {
166 checkRefCount();
167 int itemIndex = getItemIndex(index);
168 return ByteBufferUtils.toByte(this.items[itemIndex], index - this.itemBeginPos[itemIndex]);
171 @Override
172 public byte getByteAfterPosition(int offset) {
173 checkRefCount();
174 // Mostly the index specified will land within this current item. Short circuit for that
175 int index = offset + this.position();
176 int itemIndex = getItemIndexFromCurItemIndex(index);
177 return ByteBufferUtils.toByte(this.items[itemIndex], index - this.itemBeginPos[itemIndex]);
181 * Returns in which sub ByteBuffer, the given element index will be available.
183 private int getItemIndex(int elemIndex) {
184 if (elemIndex < 0) {
185 throw new IndexOutOfBoundsException();
187 int index = 1;
188 while (elemIndex >= this.itemBeginPos[index]) {
189 index++;
190 if (index == this.itemBeginPos.length) {
191 throw new IndexOutOfBoundsException();
194 return index - 1;
198 * Returns in which sub ByteBuffer, the given element index will be available. In this case we are
199 * sure that the item will be after MBB's current position
201 private int getItemIndexFromCurItemIndex(int elemIndex) {
202 int index = this.curItemIndex;
203 while (elemIndex >= this.itemBeginPos[index]) {
204 index++;
205 if (index == this.itemBeginPos.length) {
206 throw new IndexOutOfBoundsException();
209 return index - 1;
213 * Fetches the int at the given index. Does not change position of the underlying ByteBuffers
214 * @param index
215 * @return the int value at the given index
217 @Override
218 public int getInt(int index) {
219 checkRefCount();
220 // Mostly the index specified will land within this current item. Short circuit for that
221 int itemIndex;
222 if (this.itemBeginPos[this.curItemIndex] <= index
223 && this.itemBeginPos[this.curItemIndex + 1] > index) {
224 itemIndex = this.curItemIndex;
225 } else {
226 itemIndex = getItemIndex(index);
228 return getInt(index, itemIndex);
231 @Override
232 public int getIntAfterPosition(int offset) {
233 checkRefCount();
234 // Mostly the index specified will land within this current item. Short circuit for that
235 int index = offset + this.position();
236 int itemIndex;
237 if (this.itemBeginPos[this.curItemIndex + 1] > index) {
238 itemIndex = this.curItemIndex;
239 } else {
240 itemIndex = getItemIndexFromCurItemIndex(index);
242 return getInt(index, itemIndex);
246 * Fetches the short at the given index. Does not change position of the underlying ByteBuffers
247 * @param index
248 * @return the short value at the given index
250 @Override
251 public short getShort(int index) {
252 checkRefCount();
253 // Mostly the index specified will land within this current item. Short circuit for that
254 int itemIndex;
255 if (this.itemBeginPos[this.curItemIndex] <= index
256 && this.itemBeginPos[this.curItemIndex + 1] > index) {
257 itemIndex = this.curItemIndex;
258 } else {
259 itemIndex = getItemIndex(index);
261 ByteBuffer item = items[itemIndex];
262 int offsetInItem = index - this.itemBeginPos[itemIndex];
263 if (item.limit() - offsetInItem >= Bytes.SIZEOF_SHORT) {
264 return ByteBufferUtils.toShort(item, offsetInItem);
266 if (items.length - 1 == itemIndex) {
267 // means cur item is the last one and we wont be able to read a int. Throw exception
268 throw new BufferUnderflowException();
270 ByteBuffer nextItem = items[itemIndex + 1];
271 // Get available one byte from this item and remaining one from next
272 short n = 0;
273 n = (short) (n ^ (ByteBufferUtils.toByte(item, offsetInItem) & 0xFF));
274 n = (short) (n << 8);
275 n = (short) (n ^ (ByteBufferUtils.toByte(nextItem, 0) & 0xFF));
276 return n;
279 @Override
280 public short getShortAfterPosition(int offset) {
281 checkRefCount();
282 // Mostly the index specified will land within this current item. Short circuit for that
283 int index = offset + this.position();
284 int itemIndex;
285 if (this.itemBeginPos[this.curItemIndex + 1] > index) {
286 itemIndex = this.curItemIndex;
287 } else {
288 itemIndex = getItemIndexFromCurItemIndex(index);
290 return getShort(index, itemIndex);
293 private int getInt(int index, int itemIndex) {
294 ByteBuffer item = items[itemIndex];
295 int offsetInItem = index - this.itemBeginPos[itemIndex];
296 int remainingLen = item.limit() - offsetInItem;
297 if (remainingLen >= Bytes.SIZEOF_INT) {
298 return ByteBufferUtils.toInt(item, offsetInItem);
300 if (items.length - 1 == itemIndex) {
301 // means cur item is the last one and we wont be able to read a int. Throw exception
302 throw new BufferUnderflowException();
304 int l = 0;
305 for (int i = 0; i < Bytes.SIZEOF_INT; i++) {
306 l <<= 8;
307 l ^= get(index + i) & 0xFF;
309 return l;
312 private short getShort(int index, int itemIndex) {
313 ByteBuffer item = items[itemIndex];
314 int offsetInItem = index - this.itemBeginPos[itemIndex];
315 int remainingLen = item.limit() - offsetInItem;
316 if (remainingLen >= Bytes.SIZEOF_SHORT) {
317 return ByteBufferUtils.toShort(item, offsetInItem);
319 if (items.length - 1 == itemIndex) {
320 // means cur item is the last one and we wont be able to read a short. Throw exception
321 throw new BufferUnderflowException();
323 ByteBuffer nextItem = items[itemIndex + 1];
324 // Get available bytes from this item and remaining from next
325 short l = 0;
326 for (int i = offsetInItem; i < item.capacity(); i++) {
327 l = (short) (l << 8);
328 l = (short) (l ^ (ByteBufferUtils.toByte(item, i) & 0xFF));
330 for (int i = 0; i < Bytes.SIZEOF_SHORT - remainingLen; i++) {
331 l = (short) (l << 8);
332 l = (short) (l ^ (ByteBufferUtils.toByte(nextItem, i) & 0xFF));
334 return l;
337 private long getLong(int index, int itemIndex) {
338 ByteBuffer item = items[itemIndex];
339 int offsetInItem = index - this.itemBeginPos[itemIndex];
340 int remainingLen = item.limit() - offsetInItem;
341 if (remainingLen >= Bytes.SIZEOF_LONG) {
342 return ByteBufferUtils.toLong(item, offsetInItem);
344 if (items.length - 1 == itemIndex) {
345 // means cur item is the last one and we wont be able to read a long. Throw exception
346 throw new BufferUnderflowException();
348 long l = 0;
349 for (int i = 0; i < Bytes.SIZEOF_LONG; i++) {
350 l <<= 8;
351 l ^= get(index + i) & 0xFF;
353 return l;
357 * Fetches the long at the given index. Does not change position of the underlying ByteBuffers
358 * @param index
359 * @return the long value at the given index
361 @Override
362 public long getLong(int index) {
363 checkRefCount();
364 // Mostly the index specified will land within this current item. Short circuit for that
365 int itemIndex;
366 if (this.itemBeginPos[this.curItemIndex] <= index
367 && this.itemBeginPos[this.curItemIndex + 1] > index) {
368 itemIndex = this.curItemIndex;
369 } else {
370 itemIndex = getItemIndex(index);
372 return getLong(index, itemIndex);
375 @Override
376 public long getLongAfterPosition(int offset) {
377 checkRefCount();
378 // Mostly the index specified will land within this current item. Short circuit for that
379 int index = offset + this.position();
380 int itemIndex;
381 if (this.itemBeginPos[this.curItemIndex + 1] > index) {
382 itemIndex = this.curItemIndex;
383 } else {
384 itemIndex = getItemIndexFromCurItemIndex(index);
386 return getLong(index, itemIndex);
390 * @return this MBB's current position
392 @Override
393 public int position() {
394 checkRefCount();
395 return itemBeginPos[this.curItemIndex] + this.curItem.position();
399 * Sets this MBB's position to the given value.
400 * @param position
401 * @return this object
403 @Override
404 public MultiByteBuff position(int position) {
405 checkRefCount();
406 // Short circuit for positioning within the cur item. Mostly that is the case.
407 if (this.itemBeginPos[this.curItemIndex] <= position
408 && this.itemBeginPos[this.curItemIndex + 1] > position) {
409 this.curItem.position(position - this.itemBeginPos[this.curItemIndex]);
410 return this;
412 int itemIndex = getItemIndex(position);
413 // All items from 0 - curItem-1 set position at end.
414 for (int i = 0; i < itemIndex; i++) {
415 this.items[i].position(this.items[i].limit());
417 // All items after curItem set position at begin
418 for (int i = itemIndex + 1; i < this.items.length; i++) {
419 this.items[i].position(0);
421 this.curItem = this.items[itemIndex];
422 this.curItem.position(position - this.itemBeginPos[itemIndex]);
423 this.curItemIndex = itemIndex;
424 return this;
428 * Rewinds this MBB and the position is set to 0
429 * @return this object
431 @Override
432 public MultiByteBuff rewind() {
433 checkRefCount();
434 for (int i = 0; i < this.items.length; i++) {
435 this.items[i].rewind();
437 this.curItemIndex = 0;
438 this.curItem = this.items[this.curItemIndex];
439 this.markedItemIndex = -1;
440 return this;
444 * Marks the current position of the MBB
445 * @return this object
447 @Override
448 public MultiByteBuff mark() {
449 checkRefCount();
450 this.markedItemIndex = this.curItemIndex;
451 this.curItem.mark();
452 return this;
456 * Similar to {@link ByteBuffer}.reset(), ensures that this MBB
457 * is reset back to last marked position.
458 * @return This MBB
460 @Override
461 public MultiByteBuff reset() {
462 checkRefCount();
463 // when the buffer is moved to the next one.. the reset should happen on the previous marked
464 // item and the new one should be taken as the base
465 if (this.markedItemIndex < 0) throw new InvalidMarkException();
466 ByteBuffer markedItem = this.items[this.markedItemIndex];
467 markedItem.reset();
468 this.curItem = markedItem;
469 // All items after the marked position upto the current item should be reset to 0
470 for (int i = this.curItemIndex; i > this.markedItemIndex; i--) {
471 this.items[i].position(0);
473 this.curItemIndex = this.markedItemIndex;
474 return this;
478 * Returns the number of elements between the current position and the
479 * limit.
480 * @return the remaining elements in this MBB
482 @Override
483 public int remaining() {
484 checkRefCount();
485 int remain = 0;
486 for (int i = curItemIndex; i < items.length; i++) {
487 remain += items[i].remaining();
489 return remain;
493 * Returns true if there are elements between the current position and the limt
494 * @return true if there are elements, false otherwise
496 @Override
497 public final boolean hasRemaining() {
498 checkRefCount();
499 return this.curItem.hasRemaining() || (this.curItemIndex < this.limitedItemIndex
500 && this.items[this.curItemIndex + 1].hasRemaining());
504 * A relative method that returns byte at the current position. Increments the
505 * current position by the size of a byte.
506 * @return the byte at the current position
508 @Override
509 public byte get() {
510 checkRefCount();
511 if (this.curItem.remaining() == 0) {
512 if (items.length - 1 == this.curItemIndex) {
513 // means cur item is the last one and we wont be able to read a long. Throw exception
514 throw new BufferUnderflowException();
516 this.curItemIndex++;
517 this.curItem = this.items[this.curItemIndex];
519 return this.curItem.get();
523 * Returns the short value at the current position. Also advances the position by the size
524 * of short
526 * @return the short value at the current position
528 @Override
529 public short getShort() {
530 checkRefCount();
531 int remaining = this.curItem.remaining();
532 if (remaining >= Bytes.SIZEOF_SHORT) {
533 return this.curItem.getShort();
535 short n = 0;
536 n = (short) (n ^ (get() & 0xFF));
537 n = (short) (n << 8);
538 n = (short) (n ^ (get() & 0xFF));
539 return n;
543 * Returns the int value at the current position. Also advances the position by the size of int
545 * @return the int value at the current position
547 @Override
548 public int getInt() {
549 checkRefCount();
550 int remaining = this.curItem.remaining();
551 if (remaining >= Bytes.SIZEOF_INT) {
552 return this.curItem.getInt();
554 int n = 0;
555 for (int i = 0; i < Bytes.SIZEOF_INT; i++) {
556 n <<= 8;
557 n ^= get() & 0xFF;
559 return n;
564 * Returns the long value at the current position. Also advances the position by the size of long
566 * @return the long value at the current position
568 @Override
569 public long getLong() {
570 checkRefCount();
571 int remaining = this.curItem.remaining();
572 if (remaining >= Bytes.SIZEOF_LONG) {
573 return this.curItem.getLong();
575 long l = 0;
576 for (int i = 0; i < Bytes.SIZEOF_LONG; i++) {
577 l <<= 8;
578 l ^= get() & 0xFF;
580 return l;
584 * Copies the content from this MBB's current position to the byte array and fills it. Also
585 * advances the position of the MBB by the length of the byte[].
586 * @param dst
588 @Override
589 public void get(byte[] dst) {
590 get(dst, 0, dst.length);
594 * Copies the specified number of bytes from this MBB's current position to the byte[]'s offset.
595 * Also advances the position of the MBB by the given length.
596 * @param dst
597 * @param offset within the current array
598 * @param length upto which the bytes to be copied
600 @Override
601 public void get(byte[] dst, int offset, int length) {
602 checkRefCount();
603 while (length > 0) {
604 int toRead = Math.min(length, this.curItem.remaining());
605 ByteBufferUtils.copyFromBufferToArray(dst, this.curItem, this.curItem.position(), offset,
606 toRead);
607 this.curItem.position(this.curItem.position() + toRead);
608 length -= toRead;
609 if (length == 0) break;
610 this.curItemIndex++;
611 this.curItem = this.items[this.curItemIndex];
612 offset += toRead;
616 @Override
617 public void get(int sourceOffset, byte[] dst, int offset, int length) {
618 checkRefCount();
619 int itemIndex = getItemIndex(sourceOffset);
620 ByteBuffer item = this.items[itemIndex];
621 sourceOffset = sourceOffset - this.itemBeginPos[itemIndex];
622 while (length > 0) {
623 int toRead = Math.min((item.limit() - sourceOffset), length);
624 ByteBufferUtils.copyFromBufferToArray(dst, item, sourceOffset, offset, toRead);
625 length -= toRead;
626 if (length == 0) break;
627 itemIndex++;
628 item = this.items[itemIndex];
629 offset += toRead;
630 sourceOffset = 0;
635 * Marks the limit of this MBB.
636 * @param limit
637 * @return This MBB
639 @Override
640 public MultiByteBuff limit(int limit) {
641 checkRefCount();
642 this.limit = limit;
643 // Normally the limit will try to limit within the last BB item
644 int limitedIndexBegin = this.itemBeginPos[this.limitedItemIndex];
645 if (limit >= limitedIndexBegin && limit < this.itemBeginPos[this.limitedItemIndex + 1]) {
646 this.items[this.limitedItemIndex].limit(limit - limitedIndexBegin);
647 return this;
649 int itemIndex = getItemIndex(limit);
650 int beginOffset = this.itemBeginPos[itemIndex];
651 int offsetInItem = limit - beginOffset;
652 ByteBuffer item = items[itemIndex];
653 item.limit(offsetInItem);
654 for (int i = this.limitedItemIndex; i < itemIndex; i++) {
655 this.items[i].limit(this.items[i].capacity());
657 this.limitedItemIndex = itemIndex;
658 for (int i = itemIndex + 1; i < this.items.length; i++) {
659 this.items[i].limit(this.items[i].position());
661 return this;
665 * Returns the limit of this MBB
666 * @return limit of the MBB
668 @Override
669 public int limit() {
670 return this.limit;
674 * Returns an MBB which is a sliced version of this MBB. The position, limit and mark
675 * of the new MBB will be independent than that of the original MBB.
676 * The content of the new MBB will start at this MBB's current position
677 * @return a sliced MBB
679 @Override
680 public MultiByteBuff slice() {
681 checkRefCount();
682 ByteBuffer[] copy = new ByteBuffer[this.limitedItemIndex - this.curItemIndex + 1];
683 for (int i = curItemIndex, j = 0; i <= this.limitedItemIndex; i++, j++) {
684 copy[j] = this.items[i].slice();
686 return new MultiByteBuff(refCnt, copy);
690 * Returns an MBB which is a duplicate version of this MBB. The position, limit and mark of the
691 * new MBB will be independent than that of the original MBB. The content of the new MBB will
692 * start at this MBB's current position The position, limit and mark of the new MBB would be
693 * identical to this MBB in terms of values.
694 * @return a duplicated MBB
696 @Override
697 public MultiByteBuff duplicate() {
698 checkRefCount();
699 ByteBuffer[] itemsCopy = new ByteBuffer[this.items.length];
700 for (int i = 0; i < this.items.length; i++) {
701 itemsCopy[i] = items[i].duplicate();
703 return new MultiByteBuff(refCnt, itemsCopy, this.itemBeginPos, this.limit,
704 this.limitedItemIndex, this.curItemIndex, this.markedItemIndex);
708 * Writes a byte to this MBB at the current position and increments the position
709 * @param b
710 * @return this object
712 @Override
713 public MultiByteBuff put(byte b) {
714 checkRefCount();
715 if (this.curItem.remaining() == 0) {
716 if (this.curItemIndex == this.items.length - 1) {
717 throw new BufferOverflowException();
719 this.curItemIndex++;
720 this.curItem = this.items[this.curItemIndex];
722 this.curItem.put(b);
723 return this;
727 * Writes a byte to this MBB at the given index and won't affect the position of any of the
728 * buffers.
729 * @return this object
730 * @throws IndexOutOfBoundsException If <tt>index</tt> is negative or not smaller than the
731 * {@link MultiByteBuff#limit}
733 @Override
734 public MultiByteBuff put(int index, byte b) {
735 checkRefCount();
736 int itemIndex = getItemIndex(index);
737 ByteBuffer item = items[itemIndex];
738 item.put(index - itemBeginPos[itemIndex], b);
739 return this;
743 * Copies from a src BB to this MBB. This will be absolute positional copying and won't affect the
744 * position of any of the buffers.
745 * @param destOffset the position in this MBB to which the copy should happen
746 * @param src the src MBB
747 * @param srcOffset the offset in the src MBB from where the elements should be read
748 * @param length the length upto which the copy should happen
749 * @throws BufferUnderflowException If there are fewer than length bytes remaining in src
750 * ByteBuff.
751 * @throws BufferOverflowException If there is insufficient available space in this MBB for length
752 * bytes.
754 @Override
755 public MultiByteBuff put(int destOffset, ByteBuff src, int srcOffset, int length) {
756 checkRefCount();
757 int destItemIndex = getItemIndex(destOffset);
758 int srcItemIndex = getItemIndexForByteBuff(src, srcOffset, length);
760 ByteBuffer destItem = this.items[destItemIndex];
761 destOffset = this.getRelativeOffset(destOffset, destItemIndex);
763 ByteBuffer srcItem = getItemByteBuffer(src, srcItemIndex);
764 srcOffset = getRelativeOffsetForByteBuff(src, srcOffset, srcItemIndex);
766 while (length > 0) {
767 int toWrite = destItem.limit() - destOffset;
768 if (toWrite <= 0) {
769 throw new BufferOverflowException();
771 int toRead = srcItem.limit() - srcOffset;
772 if (toRead <= 0) {
773 throw new BufferUnderflowException();
775 int toMove = Math.min(length, Math.min(toRead, toWrite));
776 ByteBufferUtils.copyFromBufferToBuffer(srcItem, destItem, srcOffset, destOffset, toMove);
777 length -= toMove;
778 if (length == 0) {
779 break;
781 if (toRead < toWrite) {
782 if (++srcItemIndex >= getItemByteBufferCount(src)) {
783 throw new BufferUnderflowException();
785 srcItem = getItemByteBuffer(src, srcItemIndex);
786 srcOffset = 0;
787 destOffset += toMove;
788 } else if (toRead > toWrite) {
789 if (++destItemIndex >= this.items.length) {
790 throw new BufferOverflowException();
792 destItem = this.items[destItemIndex];
793 destOffset = 0;
794 srcOffset += toMove;
795 } else {
796 // toRead = toWrite case
797 if (++srcItemIndex >= getItemByteBufferCount(src)) {
798 throw new BufferUnderflowException();
800 srcItem = getItemByteBuffer(src, srcItemIndex);
801 srcOffset = 0;
802 if (++destItemIndex >= this.items.length) {
803 throw new BufferOverflowException();
805 destItem = this.items[destItemIndex];
806 destOffset = 0;
809 return this;
812 private static ByteBuffer getItemByteBuffer(ByteBuff buf, int byteBufferIndex) {
813 if (buf instanceof SingleByteBuff) {
814 if (byteBufferIndex != 0) {
815 throw new IndexOutOfBoundsException(
816 "index:[" + byteBufferIndex + "],but only index 0 is valid.");
818 return buf.nioByteBuffers()[0];
820 MultiByteBuff multiByteBuff = (MultiByteBuff) buf;
821 if (byteBufferIndex < 0 || byteBufferIndex >= multiByteBuff.items.length) {
822 throw new IndexOutOfBoundsException(
823 "index:[" + byteBufferIndex + "],but only index [0-" + multiByteBuff.items.length
824 + ") is valid.");
826 return multiByteBuff.items[byteBufferIndex];
829 private static int getItemIndexForByteBuff(ByteBuff byteBuff, int offset, int length) {
830 if (byteBuff instanceof SingleByteBuff) {
831 ByteBuffer byteBuffer = byteBuff.nioByteBuffers()[0];
832 if (offset + length > byteBuffer.limit()) {
833 throw new BufferUnderflowException();
835 return 0;
837 MultiByteBuff multiByteBuff = (MultiByteBuff) byteBuff;
838 return multiByteBuff.getItemIndex(offset);
841 private static int getRelativeOffsetForByteBuff(ByteBuff byteBuff, int globalOffset,
842 int itemIndex) {
843 if (byteBuff instanceof SingleByteBuff) {
844 if (itemIndex != 0) {
845 throw new IndexOutOfBoundsException("index:[" + itemIndex + "],but only index 0 is valid.");
847 return globalOffset;
849 return ((MultiByteBuff) byteBuff).getRelativeOffset(globalOffset, itemIndex);
852 private int getRelativeOffset(int globalOffset, int itemIndex) {
853 if (itemIndex < 0 || itemIndex >= this.items.length) {
854 throw new IndexOutOfBoundsException(
855 "index:[" + itemIndex + "],but only index [0-" + this.items.length + ") is valid.");
857 return globalOffset - this.itemBeginPos[itemIndex];
860 private static int getItemByteBufferCount(ByteBuff buf) {
861 return (buf instanceof SingleByteBuff) ? 1 : ((MultiByteBuff) buf).items.length;
865 * Writes an int to this MBB at its current position. Also advances the position by size of int
866 * @param val Int value to write
867 * @return this object
869 @Override
870 public MultiByteBuff putInt(int val) {
871 checkRefCount();
872 if (this.curItem.remaining() >= Bytes.SIZEOF_INT) {
873 this.curItem.putInt(val);
874 return this;
876 if (this.curItemIndex == this.items.length - 1) {
877 throw new BufferOverflowException();
879 // During read, we will read as byte by byte for this case. So just write in Big endian
880 put(int3(val));
881 put(int2(val));
882 put(int1(val));
883 put(int0(val));
884 return this;
887 private static byte int3(int x) {
888 return (byte) (x >> 24);
891 private static byte int2(int x) {
892 return (byte) (x >> 16);
895 private static byte int1(int x) {
896 return (byte) (x >> 8);
899 private static byte int0(int x) {
900 return (byte) (x);
904 * Copies from the given byte[] to this MBB
905 * @param src
906 * @return this MBB
908 @Override
909 public final MultiByteBuff put(byte[] src) {
910 return put(src, 0, src.length);
914 * Copies from the given byte[] to this MBB
915 * @param src
916 * @param offset the position in the byte array from which the copy should be done
917 * @param length the length upto which the copy should happen
918 * @return this MBB
920 @Override
921 public MultiByteBuff put(byte[] src, int offset, int length) {
922 checkRefCount();
923 if (this.curItem.remaining() >= length) {
924 ByteBufferUtils.copyFromArrayToBuffer(this.curItem, src, offset, length);
925 return this;
927 int end = offset + length;
928 for (int i = offset; i < end; i++) {
929 this.put(src[i]);
931 return this;
936 * Writes a long to this MBB at its current position. Also advances the position by size of long
937 * @param val Long value to write
938 * @return this object
940 @Override
941 public MultiByteBuff putLong(long val) {
942 checkRefCount();
943 if (this.curItem.remaining() >= Bytes.SIZEOF_LONG) {
944 this.curItem.putLong(val);
945 return this;
947 if (this.curItemIndex == this.items.length - 1) {
948 throw new BufferOverflowException();
950 // During read, we will read as byte by byte for this case. So just write in Big endian
951 put(long7(val));
952 put(long6(val));
953 put(long5(val));
954 put(long4(val));
955 put(long3(val));
956 put(long2(val));
957 put(long1(val));
958 put(long0(val));
959 return this;
962 private static byte long7(long x) {
963 return (byte) (x >> 56);
966 private static byte long6(long x) {
967 return (byte) (x >> 48);
970 private static byte long5(long x) {
971 return (byte) (x >> 40);
974 private static byte long4(long x) {
975 return (byte) (x >> 32);
978 private static byte long3(long x) {
979 return (byte) (x >> 24);
982 private static byte long2(long x) {
983 return (byte) (x >> 16);
986 private static byte long1(long x) {
987 return (byte) (x >> 8);
990 private static byte long0(long x) {
991 return (byte) (x);
995 * Jumps the current position of this MBB by specified length.
996 * @param length
998 @Override
999 public MultiByteBuff skip(int length) {
1000 checkRefCount();
1001 // Get available bytes from this item and remaining from next
1002 int jump = 0;
1003 while (true) {
1004 jump = this.curItem.remaining();
1005 if (jump >= length) {
1006 this.curItem.position(this.curItem.position() + length);
1007 break;
1009 this.curItem.position(this.curItem.position() + jump);
1010 length -= jump;
1011 this.curItemIndex++;
1012 this.curItem = this.items[this.curItemIndex];
1014 return this;
1018 * Jumps back the current position of this MBB by specified length.
1019 * @param length
1021 @Override
1022 public MultiByteBuff moveBack(int length) {
1023 checkRefCount();
1024 while (length != 0) {
1025 if (length > curItem.position()) {
1026 length -= curItem.position();
1027 this.curItem.position(0);
1028 this.curItemIndex--;
1029 this.curItem = this.items[curItemIndex];
1030 } else {
1031 this.curItem.position(curItem.position() - length);
1032 break;
1035 return this;
1039 * Returns bytes from current position till length specified, as a single ByteBuffer. When all
1040 * these bytes happen to be in a single ByteBuffer, which this object wraps, that ByteBuffer item
1041 * as such will be returned. So users are warned not to change the position or limit of this
1042 * returned ByteBuffer. The position of the returned byte buffer is at the begin of the required
1043 * bytes. When the required bytes happen to span across multiple ByteBuffers, this API will copy
1044 * the bytes to a newly created ByteBuffer of required size and return that.
1046 * @param length number of bytes required.
1047 * @return bytes from current position till length specified, as a single ByteButter.
1049 @Override
1050 public ByteBuffer asSubByteBuffer(int length) {
1051 checkRefCount();
1052 if (this.curItem.remaining() >= length) {
1053 return this.curItem;
1055 int offset = 0;
1056 byte[] dupB = new byte[length];
1057 int locCurItemIndex = curItemIndex;
1058 ByteBuffer locCurItem = curItem;
1059 while (length > 0) {
1060 int toRead = Math.min(length, locCurItem.remaining());
1061 ByteBufferUtils.copyFromBufferToArray(dupB, locCurItem, locCurItem.position(), offset,
1062 toRead);
1063 length -= toRead;
1064 if (length == 0) break;
1065 locCurItemIndex++;
1066 locCurItem = this.items[locCurItemIndex];
1067 offset += toRead;
1069 return ByteBuffer.wrap(dupB);
1073 * Returns bytes from given offset till length specified, as a single ByteBuffer. When all these
1074 * bytes happen to be in a single ByteBuffer, which this object wraps, that ByteBuffer item as
1075 * such will be returned (with offset in this ByteBuffer where the bytes starts). So users are
1076 * warned not to change the position or limit of this returned ByteBuffer. When the required bytes
1077 * happen to span across multiple ByteBuffers, this API will copy the bytes to a newly created
1078 * ByteBuffer of required size and return that.
1080 * @param offset the offset in this MBB from where the subBuffer should be created
1081 * @param length the length of the subBuffer
1082 * @param pair a pair that will have the bytes from the current position till length specified, as
1083 * a single ByteBuffer and offset in that Buffer where the bytes starts. The method would
1084 * set the values on the pair that is passed in by the caller
1086 @Override
1087 public void asSubByteBuffer(int offset, int length, ObjectIntPair<ByteBuffer> pair) {
1088 checkRefCount();
1089 if (this.itemBeginPos[this.curItemIndex] <= offset) {
1090 int relOffsetInCurItem = offset - this.itemBeginPos[this.curItemIndex];
1091 if (this.curItem.limit() - relOffsetInCurItem >= length) {
1092 pair.setFirst(this.curItem);
1093 pair.setSecond(relOffsetInCurItem);
1094 return;
1097 int itemIndex = getItemIndex(offset);
1098 ByteBuffer item = this.items[itemIndex];
1099 offset = offset - this.itemBeginPos[itemIndex];
1100 if (item.limit() - offset >= length) {
1101 pair.setFirst(item);
1102 pair.setSecond(offset);
1103 return;
1105 byte[] dst = new byte[length];
1106 int destOffset = 0;
1107 while (length > 0) {
1108 int toRead = Math.min(length, item.limit() - offset);
1109 ByteBufferUtils.copyFromBufferToArray(dst, item, offset, destOffset, toRead);
1110 length -= toRead;
1111 if (length == 0) break;
1112 itemIndex++;
1113 item = this.items[itemIndex];
1114 destOffset += toRead;
1115 offset = 0;
1117 pair.setFirst(ByteBuffer.wrap(dst));
1118 pair.setSecond(0);
1122 * Copies the content from an this MBB to a ByteBuffer
1123 * @param out the ByteBuffer to which the copy has to happen, its position will be advanced.
1124 * @param sourceOffset the offset in the MBB from which the elements has to be copied
1125 * @param length the length in the MBB upto which the elements has to be copied
1127 @Override
1128 public void get(ByteBuffer out, int sourceOffset, int length) {
1129 checkRefCount();
1130 int itemIndex = getItemIndex(sourceOffset);
1131 ByteBuffer in = this.items[itemIndex];
1132 sourceOffset = sourceOffset - this.itemBeginPos[itemIndex];
1133 while (length > 0) {
1134 int toRead = Math.min(in.limit() - sourceOffset, length);
1135 ByteBufferUtils.copyFromBufferToBuffer(in, out, sourceOffset, toRead);
1136 length -= toRead;
1137 if (length == 0) {
1138 break;
1140 itemIndex++;
1141 in = this.items[itemIndex];
1142 sourceOffset = 0;
1147 * Copy the content from this MBB to a byte[] based on the given offset and
1148 * length
1150 * @param offset
1151 * the position from where the copy should start
1152 * @param length
1153 * the length upto which the copy has to be done
1154 * @return byte[] with the copied contents from this MBB.
1156 @Override
1157 public byte[] toBytes(int offset, int length) {
1158 checkRefCount();
1159 byte[] output = new byte[length];
1160 this.get(offset, output, 0, length);
1161 return output;
1164 private int internalRead(ReadableByteChannel channel, long offset,
1165 ChannelReader reader) throws IOException {
1166 checkRefCount();
1167 int total = 0;
1168 while (buffsIterator.hasNext()) {
1169 ByteBuffer buffer = buffsIterator.next();
1170 int len = read(channel, buffer, offset, reader);
1171 if (len > 0) {
1172 total += len;
1173 offset += len;
1175 if (buffer.hasRemaining()) {
1176 break;
1179 return total;
1182 @Override
1183 public int read(ReadableByteChannel channel) throws IOException {
1184 return internalRead(channel, 0, CHANNEL_READER);
1187 @Override
1188 public int read(FileChannel channel, long offset) throws IOException {
1189 return internalRead(channel, offset, FILE_READER);
1192 @Override
1193 public int write(FileChannel channel, long offset) throws IOException {
1194 checkRefCount();
1195 int total = 0;
1196 while (buffsIterator.hasNext()) {
1197 ByteBuffer buffer = buffsIterator.next();
1198 while (buffer.hasRemaining()) {
1199 int len = channel.write(buffer, offset);
1200 total += len;
1201 offset += len;
1204 return total;
1207 @Override
1208 public ByteBuffer[] nioByteBuffers() {
1209 checkRefCount();
1210 return this.items;
1213 @Override
1214 public boolean equals(Object obj) {
1215 if (!(obj instanceof MultiByteBuff)) return false;
1216 if (this == obj) return true;
1217 MultiByteBuff that = (MultiByteBuff) obj;
1218 if (this.capacity() != that.capacity()) return false;
1219 if (ByteBuff.compareTo(this, this.position(), this.limit(), that, that.position(),
1220 that.limit()) == 0) {
1221 return true;
1223 return false;
1226 @Override
1227 public int hashCode() {
1228 int hash = 0;
1229 for (ByteBuffer b : this.items) {
1230 hash += b.hashCode();
1232 return hash;
1235 @Override
1236 public MultiByteBuff retain() {
1237 refCnt.retain();
1238 return this;