/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.hadoop.hbase.nio; import java.io.IOException; import java.nio.BufferOverflowException; import java.nio.BufferUnderflowException; import java.nio.ByteBuffer; import java.nio.InvalidMarkException; import java.nio.channels.ReadableByteChannel; import org.apache.hadoop.hbase.classification.InterfaceAudience; import org.apache.hadoop.hbase.util.ByteBufferUtils; import org.apache.hadoop.hbase.util.Bytes; import org.apache.hadoop.hbase.util.ObjectIntPair; import com.google.common.annotations.VisibleForTesting; /** * Provides a unified view of all the underlying ByteBuffers and will look as if a bigger * sequential buffer. This class provides similar APIs as in {@link ByteBuffer} to put/get int, * short, long etc and doing operations like mark, reset, slice etc. This has to be used when * data is split across multiple byte buffers and we don't want copy them to single buffer * for reading from it. */ @InterfaceAudience.Private public class MultiByteBuff extends ByteBuff { private final ByteBuffer[] items; // Pointer to the current item in the MBB private ByteBuffer curItem = null; // Index of the current item in the MBB private int curItemIndex = 0; private int limit = 0; private int limitedItemIndex; private int markedItemIndex = -1; private final int[] itemBeginPos; public MultiByteBuff(ByteBuffer... items) { assert items != null; assert items.length > 0; this.items = items; this.curItem = this.items[this.curItemIndex]; // See below optimization in getInt(int) where we check whether the given index land in current // item. For this we need to check whether the passed index is less than the next item begin // offset. To handle this effectively for the last item buffer, we add an extra item into this // array. itemBeginPos = new int[items.length + 1]; int offset = 0; for (int i = 0; i < items.length; i++) { ByteBuffer item = items[i]; item.rewind(); itemBeginPos[i] = offset; int l = item.limit() - item.position(); offset += l; } this.limit = offset; this.itemBeginPos[items.length] = offset + 1; this.limitedItemIndex = this.items.length - 1; } private MultiByteBuff(ByteBuffer[] items, int[] itemBeginPos, int limit, int limitedIndex, int curItemIndex, int markedIndex) { this.items = items; this.curItemIndex = curItemIndex; this.curItem = this.items[this.curItemIndex]; this.itemBeginPos = itemBeginPos; this.limit = limit; this.limitedItemIndex = limitedIndex; this.markedItemIndex = markedIndex; } /** * @throws UnsupportedOperationException MBB does not support * array based operations */ @Override public byte[] array() { throw new UnsupportedOperationException(); } /** * @throws UnsupportedOperationException MBB does not * support array based operations */ @Override public int arrayOffset() { throw new UnsupportedOperationException(); } /** * @return false. MBB does not support array based operations */ @Override public boolean hasArray() { return false; } /** * @return the total capacity of this MultiByteBuffer. */ @Override public int capacity() { int c = 0; for (ByteBuffer item : this.items) { c += item.capacity(); } return c; } /** * Fetches the byte at the given index. Does not change position of the underlying ByteBuffers * @param index * @return the byte at the given index */ @Override public byte get(int index) { int itemIndex = getItemIndex(index); return ByteBufferUtils.toByte(this.items[itemIndex], index - this.itemBeginPos[itemIndex]); } @Override public byte getByteAfterPosition(int offset) { // Mostly the index specified will land within this current item. Short circuit for that int index = offset + this.position(); int itemIndex = getItemIndexFromCurItemIndex(index); return ByteBufferUtils.toByte(this.items[itemIndex], index - this.itemBeginPos[itemIndex]); } /* * Returns in which sub ByteBuffer, the given element index will be available. */ private int getItemIndex(int elemIndex) { int index = 1; while (elemIndex >= this.itemBeginPos[index]) { index++; if (index == this.itemBeginPos.length) { throw new IndexOutOfBoundsException(); } } return index - 1; } /* * Returns in which sub ByteBuffer, the given element index will be available. In this case we are * sure that the item will be after MBB's current position */ private int getItemIndexFromCurItemIndex(int elemIndex) { int index = this.curItemIndex; while (elemIndex >= this.itemBeginPos[index]) { index++; if (index == this.itemBeginPos.length) { throw new IndexOutOfBoundsException(); } } return index - 1; } /** * Fetches the int at the given index. Does not change position of the underlying ByteBuffers * @param index * @return the int value at the given index */ public int getInt(int index) { // Mostly the index specified will land within this current item. Short circuit for that int itemIndex; if (this.itemBeginPos[this.curItemIndex] <= index && this.itemBeginPos[this.curItemIndex + 1] > index) { itemIndex = this.curItemIndex; } else { itemIndex = getItemIndex(index); } return getInt(index, itemIndex); } @Override public int getIntAfterPosition(int offset) { // Mostly the index specified will land within this current item. Short circuit for that int index = offset + this.position(); int itemIndex; if (this.itemBeginPos[this.curItemIndex + 1] > index) { itemIndex = this.curItemIndex; } else { itemIndex = getItemIndexFromCurItemIndex(index); } return getInt(index, itemIndex); } /** * Fetches the short at the given index. Does not change position of the underlying ByteBuffers * @param index * @return the short value at the given index */ public short getShort(int index) { // Mostly the index specified will land within this current item. Short circuit for that int itemIndex; if (this.itemBeginPos[this.curItemIndex] <= index && this.itemBeginPos[this.curItemIndex + 1] > index) { itemIndex = this.curItemIndex; } else { itemIndex = getItemIndex(index); } ByteBuffer item = items[itemIndex]; int offsetInItem = index - this.itemBeginPos[itemIndex]; if (item.limit() - offsetInItem >= Bytes.SIZEOF_SHORT) { return ByteBufferUtils.toShort(item, offsetInItem); } if (items.length - 1 == itemIndex) { // means cur item is the last one and we wont be able to read a int. Throw exception throw new BufferUnderflowException(); } ByteBuffer nextItem = items[itemIndex + 1]; // Get available one byte from this item and remaining one from next short n = 0; n ^= ByteBufferUtils.toByte(item, offsetInItem) & 0xFF; n <<= 8; n ^= ByteBufferUtils.toByte(nextItem, 0) & 0xFF; return n; } @Override public short getShortAfterPosition(int offset) { // Mostly the index specified will land within this current item. Short circuit for that int index = offset + this.position(); int itemIndex; if (this.itemBeginPos[this.curItemIndex + 1] > index) { itemIndex = this.curItemIndex; } else { itemIndex = getItemIndexFromCurItemIndex(index); } return getShort(index, itemIndex); } private int getInt(int index, int itemIndex) { ByteBuffer item = items[itemIndex]; int offsetInItem = index - this.itemBeginPos[itemIndex]; int remainingLen = item.limit() - offsetInItem; if (remainingLen >= Bytes.SIZEOF_INT) { return ByteBufferUtils.toInt(item, offsetInItem); } if (items.length - 1 == itemIndex) { // means cur item is the last one and we wont be able to read a int. Throw exception throw new BufferUnderflowException(); } ByteBuffer nextItem = items[itemIndex + 1]; // Get available bytes from this item and remaining from next int l = 0; for (int i = offsetInItem; i < item.capacity(); i++) { l <<= 8; l ^= ByteBufferUtils.toByte(item, i) & 0xFF; } for (int i = 0; i < Bytes.SIZEOF_INT - remainingLen; i++) { l <<= 8; l ^= ByteBufferUtils.toByte(nextItem, i) & 0xFF; } return l; } private short getShort(int index, int itemIndex) { ByteBuffer item = items[itemIndex]; int offsetInItem = index - this.itemBeginPos[itemIndex]; int remainingLen = item.limit() - offsetInItem; if (remainingLen >= Bytes.SIZEOF_SHORT) { return ByteBufferUtils.toShort(item, offsetInItem); } if (items.length - 1 == itemIndex) { // means cur item is the last one and we wont be able to read a int. Throw exception throw new BufferUnderflowException(); } ByteBuffer nextItem = items[itemIndex + 1]; // Get available bytes from this item and remaining from next short l = 0; for (int i = offsetInItem; i < item.capacity(); i++) { l <<= 8; l ^= ByteBufferUtils.toByte(item, i) & 0xFF; } for (int i = 0; i < Bytes.SIZEOF_SHORT - remainingLen; i++) { l <<= 8; l ^= ByteBufferUtils.toByte(nextItem, i) & 0xFF; } return l; } private long getLong(int index, int itemIndex) { ByteBuffer item = items[itemIndex]; int offsetInItem = index - this.itemBeginPos[itemIndex]; int remainingLen = item.limit() - offsetInItem; if (remainingLen >= Bytes.SIZEOF_LONG) { return ByteBufferUtils.toLong(item, offsetInItem); } if (items.length - 1 == itemIndex) { // means cur item is the last one and we wont be able to read a long. Throw exception throw new BufferUnderflowException(); } ByteBuffer nextItem = items[itemIndex + 1]; // Get available bytes from this item and remaining from next long l = 0; for (int i = offsetInItem; i < item.capacity(); i++) { l <<= 8; l ^= ByteBufferUtils.toByte(item, i) & 0xFF; } for (int i = 0; i < Bytes.SIZEOF_LONG - remainingLen; i++) { l <<= 8; l ^= ByteBufferUtils.toByte(nextItem, i) & 0xFF; } return l; } /** * Fetches the long at the given index. Does not change position of the underlying ByteBuffers * @param index * @return the long value at the given index */ public long getLong(int index) { // Mostly the index specified will land within this current item. Short circuit for that int itemIndex; if (this.itemBeginPos[this.curItemIndex] <= index && this.itemBeginPos[this.curItemIndex + 1] > index) { itemIndex = this.curItemIndex; } else { itemIndex = getItemIndex(index); } ByteBuffer item = items[itemIndex]; int offsetInItem = index - this.itemBeginPos[itemIndex]; int remainingLen = item.limit() - offsetInItem; if (remainingLen >= Bytes.SIZEOF_LONG) { return ByteBufferUtils.toLong(item, offsetInItem); } if (items.length - 1 == itemIndex) { // means cur item is the last one and we wont be able to read a long. Throw exception throw new BufferUnderflowException(); } ByteBuffer nextItem = items[itemIndex + 1]; // Get available bytes from this item and remaining from next long l = 0; for (int i = offsetInItem; i < item.capacity(); i++) { l <<= 8; l ^= ByteBufferUtils.toByte(item, i) & 0xFF; } for (int i = 0; i < Bytes.SIZEOF_LONG - remainingLen; i++) { l <<= 8; l ^= ByteBufferUtils.toByte(nextItem, i) & 0xFF; } return l; } @Override public long getLongAfterPosition(int offset) { // Mostly the index specified will land within this current item. Short circuit for that int index = offset + this.position(); int itemIndex; if (this.itemBeginPos[this.curItemIndex + 1] > index) { itemIndex = this.curItemIndex; } else { itemIndex = getItemIndexFromCurItemIndex(index); } return getLong(index, itemIndex); } /** * @return this MBB's current position */ @Override public int position() { return itemBeginPos[this.curItemIndex] + this.curItem.position(); } /** * Sets this MBB's position to the given value. * @param position * @return this object */ @Override public MultiByteBuff position(int position) { // Short circuit for positioning within the cur item. Mostly that is the case. if (this.itemBeginPos[this.curItemIndex] <= position && this.itemBeginPos[this.curItemIndex + 1] > position) { this.curItem.position(position - this.itemBeginPos[this.curItemIndex]); return this; } int itemIndex = getItemIndex(position); // All items from 0 - curItem-1 set position at end. for (int i = 0; i < itemIndex; i++) { this.items[i].position(this.items[i].limit()); } // All items after curItem set position at begin for (int i = itemIndex + 1; i < this.items.length; i++) { this.items[i].position(0); } this.curItem = this.items[itemIndex]; this.curItem.position(position - this.itemBeginPos[itemIndex]); this.curItemIndex = itemIndex; return this; } /** * Rewinds this MBB and the position is set to 0 * @return this object */ @Override public MultiByteBuff rewind() { for (int i = 0; i < this.items.length; i++) { this.items[i].rewind(); } this.curItemIndex = 0; this.curItem = this.items[this.curItemIndex]; this.markedItemIndex = -1; return this; } /** * Marks the current position of the MBB * @return this object */ @Override public MultiByteBuff mark() { this.markedItemIndex = this.curItemIndex; this.curItem.mark(); return this; } /** * Similar to {@link ByteBuffer}.reset(), ensures that this MBB * is reset back to last marked position. * @return This MBB */ @Override public MultiByteBuff reset() { // when the buffer is moved to the next one.. the reset should happen on the previous marked // item and the new one should be taken as the base if (this.markedItemIndex < 0) throw new InvalidMarkException(); ByteBuffer markedItem = this.items[this.markedItemIndex]; markedItem.reset(); this.curItem = markedItem; // All items after the marked position upto the current item should be reset to 0 for (int i = this.curItemIndex; i > this.markedItemIndex; i--) { this.items[i].position(0); } this.curItemIndex = this.markedItemIndex; return this; } /** * Returns the number of elements between the current position and the * limit. * @return the remaining elements in this MBB */ @Override public int remaining() { int remain = 0; for (int i = curItemIndex; i < items.length; i++) { remain += items[i].remaining(); } return remain; } /** * Returns true if there are elements between the current position and the limt * @return true if there are elements, false otherwise */ @Override public final boolean hasRemaining() { return this.curItem.hasRemaining() || (this.curItemIndex < this.limitedItemIndex && this.items[this.curItemIndex + 1].hasRemaining()); } /** * A relative method that returns byte at the current position. Increments the * current position by the size of a byte. * @return the byte at the current position */ @Override public byte get() { if (this.curItem.remaining() == 0) { if (items.length - 1 == this.curItemIndex) { // means cur item is the last one and we wont be able to read a long. Throw exception throw new BufferUnderflowException(); } this.curItemIndex++; this.curItem = this.items[this.curItemIndex]; } return this.curItem.get(); } /** * Returns the short value at the current position. Also advances the position by the size * of short * * @return the short value at the current position */ @Override public short getShort() { int remaining = this.curItem.remaining(); if (remaining >= Bytes.SIZEOF_SHORT) { return this.curItem.getShort(); } if (remaining == 0) { if (items.length - 1 == this.curItemIndex) { // means cur item is the last one and we wont be able to read a long. Throw exception throw new BufferUnderflowException(); } this.curItemIndex++; this.curItem = this.items[this.curItemIndex]; return this.curItem.getShort(); } short n = 0; n ^= get() & 0xFF; n <<= 8; n ^= get() & 0xFF; return n; } /** * Returns the int value at the current position. Also advances the position by the size of int * * @return the int value at the current position */ @Override public int getInt() { int remaining = this.curItem.remaining(); if (remaining >= Bytes.SIZEOF_INT) { return this.curItem.getInt(); } if (remaining == 0) { if (items.length - 1 == this.curItemIndex) { // means cur item is the last one and we wont be able to read a long. Throw exception throw new BufferUnderflowException(); } this.curItemIndex++; this.curItem = this.items[this.curItemIndex]; return this.curItem.getInt(); } // Get available bytes from this item and remaining from next int n = 0; for (int i = 0; i < Bytes.SIZEOF_INT; i++) { n <<= 8; n ^= get() & 0xFF; } return n; } /** * Returns the long value at the current position. Also advances the position by the size of long * * @return the long value at the current position */ @Override public long getLong() { int remaining = this.curItem.remaining(); if (remaining >= Bytes.SIZEOF_LONG) { return this.curItem.getLong(); } if (remaining == 0) { if (items.length - 1 == this.curItemIndex) { // means cur item is the last one and we wont be able to read a long. Throw exception throw new BufferUnderflowException(); } this.curItemIndex++; this.curItem = this.items[this.curItemIndex]; return this.curItem.getLong(); } // Get available bytes from this item and remaining from next long l = 0; for (int i = 0; i < Bytes.SIZEOF_LONG; i++) { l <<= 8; l ^= get() & 0xFF; } return l; } /** * Copies the content from this MBB's current position to the byte array and fills it. Also * advances the position of the MBB by the length of the byte[]. * @param dst */ @Override public void get(byte[] dst) { get(dst, 0, dst.length); } /** * Copies the specified number of bytes from this MBB's current position to the byte[]'s offset. * Also advances the position of the MBB by the given length. * @param dst * @param offset within the current array * @param length upto which the bytes to be copied */ @Override public void get(byte[] dst, int offset, int length) { while (length > 0) { int toRead = Math.min(length, this.curItem.remaining()); ByteBufferUtils.copyFromBufferToArray(dst, this.curItem, this.curItem.position(), offset, toRead); this.curItem.position(this.curItem.position() + toRead); length -= toRead; if (length == 0) break; this.curItemIndex++; this.curItem = this.items[this.curItemIndex]; offset += toRead; } } @Override public void get(int sourceOffset, byte[] dst, int offset, int length) { int itemIndex = getItemIndex(sourceOffset); ByteBuffer item = this.items[itemIndex]; sourceOffset = sourceOffset - this.itemBeginPos[itemIndex]; while (length > 0) { int toRead = Math.min((item.limit() - sourceOffset), length); ByteBufferUtils.copyFromBufferToArray(dst, item, sourceOffset, offset, toRead); length -= toRead; if (length == 0) break; itemIndex++; item = this.items[itemIndex]; offset += toRead; sourceOffset = 0; } } /** * Marks the limit of this MBB. * @param limit * @return This MBB */ @Override public MultiByteBuff limit(int limit) { this.limit = limit; // Normally the limit will try to limit within the last BB item int limitedIndexBegin = this.itemBeginPos[this.limitedItemIndex]; if (limit >= limitedIndexBegin && limit < this.itemBeginPos[this.limitedItemIndex + 1]) { this.items[this.limitedItemIndex].limit(limit - limitedIndexBegin); return this; } int itemIndex = getItemIndex(limit); int beginOffset = this.itemBeginPos[itemIndex]; int offsetInItem = limit - beginOffset; ByteBuffer item = items[itemIndex]; item.limit(offsetInItem); for (int i = this.limitedItemIndex; i < itemIndex; i++) { this.items[i].limit(this.items[i].capacity()); } this.limitedItemIndex = itemIndex; for (int i = itemIndex + 1; i < this.items.length; i++) { this.items[i].limit(this.items[i].position()); } return this; } /** * Returns the limit of this MBB * @return limit of the MBB */ @Override public int limit() { return this.limit; } /** * Returns an MBB which is a sliced version of this MBB. The position, limit and mark * of the new MBB will be independent than that of the original MBB. * The content of the new MBB will start at this MBB's current position * @return a sliced MBB */ @Override public MultiByteBuff slice() { ByteBuffer[] copy = new ByteBuffer[this.limitedItemIndex - this.curItemIndex + 1]; for (int i = curItemIndex, j = 0; i <= this.limitedItemIndex; i++, j++) { copy[j] = this.items[i].slice(); } return new MultiByteBuff(copy); } /** * Returns an MBB which is a duplicate version of this MBB. The position, limit and mark * of the new MBB will be independent than that of the original MBB. * The content of the new MBB will start at this MBB's current position * The position, limit and mark of the new MBB would be identical to this MBB in terms of * values. * @return a sliced MBB */ @Override public MultiByteBuff duplicate() { ByteBuffer[] itemsCopy = new ByteBuffer[this.items.length]; for (int i = 0; i < this.items.length; i++) { itemsCopy[i] = items[i].duplicate(); } return new MultiByteBuff(itemsCopy, this.itemBeginPos, this.limit, this.limitedItemIndex, this.curItemIndex, this.markedItemIndex); } /** * Writes a byte to this MBB at the current position and increments the position * @param b * @return this object */ @Override public MultiByteBuff put(byte b) { if (this.curItem.remaining() == 0) { if (this.curItemIndex == this.items.length - 1) { throw new BufferOverflowException(); } this.curItemIndex++; this.curItem = this.items[this.curItemIndex]; } this.curItem.put(b); return this; } /** * Writes a byte to this MBB at the given index * @param index * @param b * @return this object */ @Override public MultiByteBuff put(int index, byte b) { int itemIndex = getItemIndex(limit); ByteBuffer item = items[itemIndex]; item.put(index - itemBeginPos[itemIndex], b); return this; } /** * Copies from a src MBB to this MBB. * @param offset the position in this MBB to which the copy should happen * @param src the src MBB * @param srcOffset the offset in the src MBB from where the elements should be read * @param length the length upto which the copy should happen */ @Override public MultiByteBuff put(int offset, ByteBuff src, int srcOffset, int length) { int destItemIndex = getItemIndex(offset); int srcItemIndex = getItemIndex(srcOffset); ByteBuffer destItem = this.items[destItemIndex]; offset = offset - this.itemBeginPos[destItemIndex]; ByteBuffer srcItem = getItemByteBuffer(src, srcItemIndex); srcOffset = srcOffset - this.itemBeginPos[srcItemIndex]; int toRead, toWrite, toMove; while (length > 0) { toWrite = destItem.limit() - offset; toRead = srcItem.limit() - srcOffset; toMove = Math.min(length, Math.min(toRead, toWrite)); ByteBufferUtils.copyFromBufferToBuffer(srcItem, destItem, srcOffset, offset, toMove); length -= toMove; if (length == 0) break; if (toRead < toWrite) { srcItem = getItemByteBuffer(src, ++srcItemIndex); srcOffset = 0; offset += toMove; } else if (toRead > toWrite) { destItem = this.items[++destItemIndex]; offset = 0; srcOffset += toMove; } else { // toRead = toWrite case srcItem = getItemByteBuffer(src, ++srcItemIndex); srcOffset = 0; destItem = this.items[++destItemIndex]; offset = 0; } } return this; } private static ByteBuffer getItemByteBuffer(ByteBuff buf, int index) { return (buf instanceof SingleByteBuff) ? ((SingleByteBuff) buf).getEnclosingByteBuffer() : ((MultiByteBuff) buf).items[index]; } /** * Writes an int to this MBB at its current position. Also advances the position by size of int * @param val Int value to write * @return this object */ @Override public MultiByteBuff putInt(int val) { if (this.curItem.remaining() >= Bytes.SIZEOF_INT) { this.curItem.putInt(val); return this; } if (this.curItemIndex == this.items.length - 1) { throw new BufferOverflowException(); } // During read, we will read as byte by byte for this case. So just write in Big endian put(int3(val)); put(int2(val)); put(int1(val)); put(int0(val)); return this; } private static byte int3(int x) { return (byte) (x >> 24); } private static byte int2(int x) { return (byte) (x >> 16); } private static byte int1(int x) { return (byte) (x >> 8); } private static byte int0(int x) { return (byte) (x); } /** * Copies from the given byte[] to this MBB * @param src * @return this MBB */ @Override public final MultiByteBuff put(byte[] src) { return put(src, 0, src.length); } /** * Copies from the given byte[] to this MBB * @param src * @param offset the position in the byte array from which the copy should be done * @param length the length upto which the copy should happen * @return this MBB */ @Override public MultiByteBuff put(byte[] src, int offset, int length) { if (this.curItem.remaining() >= length) { ByteBufferUtils.copyFromArrayToBuffer(this.curItem, src, offset, length); return this; } int end = offset + length; for (int i = offset; i < end; i++) { this.put(src[i]); } return this; } /** * Writes a long to this MBB at its current position. Also advances the position by size of long * @param val Long value to write * @return this object */ @Override public MultiByteBuff putLong(long val) { if (this.curItem.remaining() >= Bytes.SIZEOF_LONG) { this.curItem.putLong(val); return this; } if (this.curItemIndex == this.items.length - 1) { throw new BufferOverflowException(); } // During read, we will read as byte by byte for this case. So just write in Big endian put(long7(val)); put(long6(val)); put(long5(val)); put(long4(val)); put(long3(val)); put(long2(val)); put(long1(val)); put(long0(val)); return this; } private static byte long7(long x) { return (byte) (x >> 56); } private static byte long6(long x) { return (byte) (x >> 48); } private static byte long5(long x) { return (byte) (x >> 40); } private static byte long4(long x) { return (byte) (x >> 32); } private static byte long3(long x) { return (byte) (x >> 24); } private static byte long2(long x) { return (byte) (x >> 16); } private static byte long1(long x) { return (byte) (x >> 8); } private static byte long0(long x) { return (byte) (x); } /** * Jumps the current position of this MBB by specified length. * @param length */ @Override public MultiByteBuff skip(int length) { // Get available bytes from this item and remaining from next int jump = 0; while (true) { jump = this.curItem.remaining(); if (jump >= length) { this.curItem.position(this.curItem.position() + length); break; } this.curItem.position(this.curItem.position() + jump); length -= jump; this.curItemIndex++; this.curItem = this.items[this.curItemIndex]; } return this; } /** * Jumps back the current position of this MBB by specified length. * @param length */ @Override public MultiByteBuff moveBack(int length) { while (length != 0) { if (length > curItem.position()) { length -= curItem.position(); this.curItem.position(0); this.curItemIndex--; this.curItem = this.items[curItemIndex]; } else { this.curItem.position(curItem.position() - length); break; } } return this; } /** * Returns bytes from current position till length specified, as a single ByteBuffer. When all * these bytes happen to be in a single ByteBuffer, which this object wraps, that ByteBuffer item * as such will be returned. So users are warned not to change the position or limit of this * returned ByteBuffer. The position of the returned byte buffer is at the begin of the required * bytes. When the required bytes happen to span across multiple ByteBuffers, this API will copy * the bytes to a newly created ByteBuffer of required size and return that. * * @param length number of bytes required. * @return bytes from current position till length specified, as a single ByteButter. */ @Override public ByteBuffer asSubByteBuffer(int length) { if (this.curItem.remaining() >= length) { return this.curItem; } int offset = 0; byte[] dupB = new byte[length]; int locCurItemIndex = curItemIndex; ByteBuffer locCurItem = curItem; while (length > 0) { int toRead = Math.min(length, locCurItem.remaining()); ByteBufferUtils .copyFromBufferToArray(dupB, locCurItem, locCurItem.position(), offset, toRead); length -= toRead; if (length == 0) break; locCurItemIndex++; locCurItem = this.items[locCurItemIndex]; offset += toRead; } return ByteBuffer.wrap(dupB); } /** * Returns bytes from given offset till length specified, as a single ByteBuffer. When all these * bytes happen to be in a single ByteBuffer, which this object wraps, that ByteBuffer item as * such will be returned (with offset in this ByteBuffer where the bytes starts). So users are * warned not to change the position or limit of this returned ByteBuffer. When the required bytes * happen to span across multiple ByteBuffers, this API will copy the bytes to a newly created * ByteBuffer of required size and return that. * * @param offset the offset in this MBB from where the subBuffer should be created * @param length the length of the subBuffer * @param pair a pair that will have the bytes from the current position till length specified, as * a single ByteBuffer and offset in that Buffer where the bytes starts. The method would * set the values on the pair that is passed in by the caller */ @Override public void asSubByteBuffer(int offset, int length, ObjectIntPair<ByteBuffer> pair) { if (this.itemBeginPos[this.curItemIndex] <= offset) { int relOffsetInCurItem = offset - this.itemBeginPos[this.curItemIndex]; if (this.curItem.limit() - relOffsetInCurItem >= length) { pair.setFirst(this.curItem); pair.setSecond(relOffsetInCurItem); return; } } int itemIndex = getItemIndex(offset); ByteBuffer item = this.items[itemIndex]; offset = offset - this.itemBeginPos[itemIndex]; if (item.limit() - offset >= length) { pair.setFirst(item); pair.setSecond(offset); return; } byte[] dst = new byte[length]; int destOffset = 0; while (length > 0) { int toRead = Math.min(length, item.limit() - offset); ByteBufferUtils.copyFromBufferToArray(dst, item, offset, destOffset, toRead); length -= toRead; if (length == 0) break; itemIndex++; item = this.items[itemIndex]; destOffset += toRead; offset = 0; } pair.setFirst(ByteBuffer.wrap(dst)); pair.setSecond(0); return; } /** * Copies the content from an this MBB to a ByteBuffer * @param out the ByteBuffer to which the copy has to happen * @param sourceOffset the offset in the MBB from which the elements has * to be copied * @param length the length in the MBB upto which the elements has to be copied */ @Override public void get(ByteBuffer out, int sourceOffset, int length) { // Not used from real read path actually. So not going with // optimization for (int i = 0; i < length; ++i) { out.put(this.get(sourceOffset + i)); } } /** * Copy the content from this MBB to a byte[] based on the given offset and * length * * @param offset * the position from where the copy should start * @param length * the length upto which the copy has to be done * @return byte[] with the copied contents from this MBB. */ @Override public byte[] toBytes(int offset, int length) { byte[] output = new byte[length]; this.get(offset, output, 0, length); return output; } @Override public int read(ReadableByteChannel channel) throws IOException { int total = 0; while (true) { // Read max possible into the current BB int len = channelRead(channel, this.curItem); if (len > 0) total += len; if (this.curItem.hasRemaining()) { // We were not able to read enough to fill the current BB itself. Means there is no point in // doing more reads from Channel. Only this much there for now. break; } else { if (this.curItemIndex >= this.limitedItemIndex) break; this.curItemIndex++; this.curItem = this.items[this.curItemIndex]; } } return total; } @Override public boolean equals(Object obj) { if (!(obj instanceof MultiByteBuff)) return false; if (this == obj) return true; MultiByteBuff that = (MultiByteBuff) obj; if (this.capacity() != that.capacity()) return false; if (ByteBuff.compareTo(this, this.position(), this.limit(), that, that.position(), that.limit()) == 0) { return true; } return false; } @Override public int hashCode() { int hash = 0; for (ByteBuffer b : this.items) { hash += b.hashCode(); } return hash; } /** * @return the ByteBuffers which this wraps. */ @VisibleForTesting public ByteBuffer[] getEnclosingByteBuffers() { return this.items; } }