You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@hbase.apache.org by an...@apache.org on 2015/06/18 19:09:14 UTC
hbase git commit: HBASE-13916 Create MultiByteBuffer an aggregation
of ByteBuffers.
Repository: hbase
Updated Branches:
refs/heads/master fedfe878f -> 8ae4b374e
HBASE-13916 Create MultiByteBuffer an aggregation of ByteBuffers.
Project: http://git-wip-us.apache.org/repos/asf/hbase/repo
Commit: http://git-wip-us.apache.org/repos/asf/hbase/commit/8ae4b374
Tree: http://git-wip-us.apache.org/repos/asf/hbase/tree/8ae4b374
Diff: http://git-wip-us.apache.org/repos/asf/hbase/diff/8ae4b374
Branch: refs/heads/master
Commit: 8ae4b374e3fa4cd54b8b83bc857d41f0678ad9b6
Parents: fedfe87
Author: anoopsjohn <an...@gmail.com>
Authored: Thu Jun 18 22:38:10 2015 +0530
Committer: anoopsjohn <an...@gmail.com>
Committed: Thu Jun 18 22:38:10 2015 +0530
----------------------------------------------------------------------
dev-support/test-patch.properties | 2 +-
.../hadoop/hbase/nio/MultiByteBuffer.java | 1047 ++++++++++++++++++
.../hadoop/hbase/util/ByteBufferUtils.java | 155 +++
.../hadoop/hbase/nio/TestMultiByteBuffer.java | 280 +++++
.../hadoop/hbase/util/TestByteBufferUtils.java | 49 +
5 files changed, 1532 insertions(+), 1 deletion(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/hbase/blob/8ae4b374/dev-support/test-patch.properties
----------------------------------------------------------------------
diff --git a/dev-support/test-patch.properties b/dev-support/test-patch.properties
index 86a61c9..5525d6e 100644
--- a/dev-support/test-patch.properties
+++ b/dev-support/test-patch.properties
@@ -21,7 +21,7 @@ MAVEN_OPTS="${MAVEN_OPTS:-"-Xmx3100M"}"
OK_RELEASEAUDIT_WARNINGS=0
# Allow four warnings. Javadoc complains about sun.misc.Unsafe use.
# See HBASE-7457, HBASE-13761
-OK_JAVADOC_WARNINGS=4
+OK_JAVADOC_WARNINGS=6
MAX_LINE_LENGTH=100
http://git-wip-us.apache.org/repos/asf/hbase/blob/8ae4b374/hbase-common/src/main/java/org/apache/hadoop/hbase/nio/MultiByteBuffer.java
----------------------------------------------------------------------
diff --git a/hbase-common/src/main/java/org/apache/hadoop/hbase/nio/MultiByteBuffer.java b/hbase-common/src/main/java/org/apache/hadoop/hbase/nio/MultiByteBuffer.java
new file mode 100644
index 0000000..c7cd29b
--- /dev/null
+++ b/hbase-common/src/main/java/org/apache/hadoop/hbase/nio/MultiByteBuffer.java
@@ -0,0 +1,1047 @@
+/**
+ * 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.nio.BufferOverflowException;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.nio.InvalidMarkException;
+
+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.Pair;
+import org.apache.hadoop.io.WritableUtils;
+
+/**
+ * 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 MultiByteBuffer {
+
+ 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;
+ /**
+ * An indicator that helps in short circuiting some of the APIs functionality
+ * if the MBB is backed by single item
+ */
+ private final boolean singleItem;
+ private int limit = 0;
+ private int limitedItemIndex;
+ private int markedItemIndex = -1;
+ private final int[] itemBeginPos;
+
+ public MultiByteBuffer(ByteBuffer... items) {
+ assert items != null;
+ assert items.length > 0;
+ this.items = items;
+ this.curItem = this.items[this.curItemIndex];
+ this.singleItem = items.length == 1;
+ // 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 MultiByteBuffer(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.singleItem = items.length == 1;
+ this.itemBeginPos = itemBeginPos;
+ this.limit = limit;
+ this.limitedItemIndex = limitedIndex;
+ this.markedItemIndex = markedIndex;
+ }
+
+ /**
+ * @return the underlying array if this MultiByteBuffer is made up of single on heap ByteBuffer.
+ * @throws UnsupportedOperationException - if the MBB is not made up of single item
+ * or if the single item is a Direct Byte Buffer
+ */
+ public byte[] array() {
+ if (hasArray()) {
+ return this.curItem.array();
+ }
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * @return the array offset of the item ByteBuffer if the MBB is made up of
+ * single on heap ByteBuffer
+ * @throws UnsupportedOperationException if the MBB is not made up of single item or
+ * the single item is a Direct byte Buffer
+ */
+ public int arrayOffset() {
+ if (hasArray()) {
+ return this.curItem.arrayOffset();
+ }
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * @return true if the MBB is made up of single item and that single item is an
+ * on heap Byte Buffer
+ */
+ public boolean hasArray() {
+ return this.singleItem && this.curItem.hasArray();
+ }
+
+ /**
+ * @return the total capacity of this MultiByteBuffer.
+ */
+ 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
+ */
+ public byte get(int index) {
+ if (singleItem) {
+ return this.curItem.get(index);
+ }
+ int itemIndex = getItemIndex(index);
+ return this.items[itemIndex].get(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 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) {
+ if (singleItem) {
+ return ByteBufferUtils.toShort(curItem, 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 ^= item.get(offsetInItem) & 0xFF;
+ n <<= 8;
+ n ^= nextItem.get(0) & 0xFF;
+ return n;
+ }
+
+ /**
+ * 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) {
+ if (singleItem) {
+ return ByteBufferUtils.toInt(this.curItem, 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);
+ }
+
+ /**
+ * Fetches the int at the given index. Does not change position of the underlying ByteBuffers. The
+ * difference for this API from {@link #getInt(int)} is the caller is sure that the index will be
+ * after the current position of this MBB.
+ *
+ * @param index
+ * @return the int value at the given index
+ */
+ public int getIntStrictlyForward(int index) {
+ if (singleItem) {
+ return ByteBufferUtils.toInt(this.curItem, index);
+ }
+ // Mostly the index specified will land within this current item. Short circuit for that
+ int itemIndex;
+ if (this.itemBeginPos[this.curItemIndex + 1] > index) {
+ itemIndex = this.curItemIndex;
+ } else {
+ itemIndex = getItemIndexFromCurItemIndex(index);
+ }
+ return getInt(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 ^= item.get(i) & 0xFF;
+ }
+ for (int i = 0; i < Bytes.SIZEOF_INT - remainingLen; i++) {
+ l <<= 8;
+ l ^= nextItem.get(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) {
+ if (singleItem) {
+ return this.curItem.getLong(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 ^= item.get(i) & 0xFF;
+ }
+ for (int i = 0; i < Bytes.SIZEOF_LONG - remainingLen; i++) {
+ l <<= 8;
+ l ^= nextItem.get(i) & 0xFF;
+ }
+ return l;
+ }
+
+ /**
+ * @return this MBB's current position
+ */
+ public int position() {
+ if (this.singleItem) return this.curItem.position();
+ return itemBeginPos[this.curItemIndex] + this.curItem.position();
+ }
+
+ /**
+ * Sets this MBB's position to the given value.
+ * @param position
+ * @return this object
+ */
+ public MultiByteBuffer position(int position) {
+ if (this.singleItem) {
+ this.curItem.position(position);
+ return this;
+ }
+ // 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
+ */
+ public MultiByteBuffer 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
+ */
+ public MultiByteBuffer 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
+ */
+ public MultiByteBuffer 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. </p>
+ * @return the remaining elements in this MBB
+ */
+ 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
+ */
+ public final boolean hasRemaining() {
+ return this.curItem.hasRemaining() || this.curItemIndex < this.items.length - 1;
+ }
+
+ /**
+ * 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
+ */
+ public byte get() {
+ if (!singleItem && 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
+ */
+ public short getShort() {
+ if (singleItem) {
+ return this.curItem.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
+ */
+ public int getInt() {
+ if (singleItem) {
+ return this.curItem.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
+ */
+ public long getLong() {
+ if (singleItem) {
+ return this.curItem.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;
+ }
+
+ /**
+ * Returns the long value, stored as variable long at the current position of this
+ * MultiByteBuffer. Also advances it's position accordingly.
+ * This is similar to {@link WritableUtils#readVLong(DataInput)} but reads from a
+ * {@link MultiByteBuffer}
+ *
+ * @return the long value at the current position
+ */
+ public long getVLong() {
+ byte firstByte = get();
+ int len = WritableUtils.decodeVIntSize(firstByte);
+ if (len == 1) {
+ return firstByte;
+ }
+ long i = 0;
+ byte b;
+ for (int idx = 0; idx < len - 1; idx++) {
+ b = get();
+ i = i << 8;
+ i = i | (b & 0xFF);
+ }
+ return (WritableUtils.isNegativeVInt(firstByte) ? (i ^ -1L) : i);
+ }
+
+ /**
+ * 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
+ * @return this object
+ */
+ public MultiByteBuffer get(byte[] dst) {
+ return 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
+ * @return this object
+ */
+ public MultiByteBuffer get(byte[] dst, int offset, int length) {
+ if (this.singleItem) {
+ this.curItem.get(dst, offset, length);
+ } else {
+ while (length > 0) {
+ int toRead = Math.min(length, this.curItem.remaining());
+ this.curItem.get(dst, offset, toRead);
+ length -= toRead;
+ if (length == 0)
+ break;
+ this.curItemIndex++;
+ this.curItem = this.items[this.curItemIndex];
+ offset += toRead;
+ }
+ }
+ return this;
+ }
+
+ /**
+ * Marks the limit of this MBB.
+ * @param limit
+ * @return This MBB
+ */
+ public MultiByteBuffer limit(int limit) {
+ this.limit = limit;
+ if (singleItem) {
+ this.curItem.limit(limit);
+ return this;
+ }
+ // 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
+ */
+ 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
+ */
+ public MultiByteBuffer slice() {
+ if (this.singleItem) {
+ return new MultiByteBuffer(curItem.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 MultiByteBuffer(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
+ */
+ public MultiByteBuffer duplicate() {
+ if (this.singleItem) {
+ return new MultiByteBuffer(new ByteBuffer[] { curItem.duplicate() }, this.itemBeginPos,
+ this.limit, this.limitedItemIndex, this.curItemIndex, this.markedItemIndex);
+ }
+ ByteBuffer[] itemsCopy = new ByteBuffer[this.items.length];
+ for (int i = 0; i < this.items.length; i++) {
+ itemsCopy[i] = items[i].duplicate();
+ }
+ return new MultiByteBuffer(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
+ */
+ public MultiByteBuffer put(byte b) {
+ if (!singleItem && 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
+ */
+ public MultiByteBuffer put(int index, byte b) {
+ if (this.singleItem) {
+ this.curItem.put(index, b);
+ return this;
+ }
+ 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
+ */
+ public void put(int offset, MultiByteBuffer src, int srcOffset, int length) {
+ if (src.hasArray() && this.hasArray()) {
+ System.arraycopy(src.array(), srcOffset + src.arrayOffset(), this.array(), this.arrayOffset()
+ + offset, length);
+ } else {
+ int destItemIndex = getItemIndex(offset);
+ int srcItemIndex = getItemIndex(srcOffset);
+ ByteBuffer destItem = this.items[destItemIndex];
+ offset = offset - this.itemBeginPos[destItemIndex];
+
+ ByteBuffer srcItem = src.items[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(destItem, srcItem, srcOffset, offset, toMove);
+ length -= toMove;
+ if (length == 0) break;
+ if (toRead < toWrite) {
+ srcItem = src.items[++srcItemIndex];
+ srcOffset = 0;
+ offset += toMove;
+ } else if (toRead > toWrite) {
+ destItem = this.items[++destItemIndex];
+ offset = 0;
+ srcOffset += toMove;
+ } else {
+ // toRead = toWrite case
+ srcItem = src.items[++srcItemIndex];
+ srcOffset = 0;
+ destItem = this.items[++destItemIndex];
+ offset = 0;
+ }
+ }
+ }
+ }
+
+ /**
+ * 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
+ */
+ public MultiByteBuffer putInt(int val) {
+ if (singleItem || 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
+ */
+ public final MultiByteBuffer 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
+ */
+ public MultiByteBuffer put(byte[] src, int offset, int length) {
+ if (singleItem || 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
+ */
+ public MultiByteBuffer putLong(long val) {
+ if (singleItem || 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
+ */
+ public void skip(int length) {
+ if (this.singleItem) {
+ this.curItem.position(this.curItem.position() + length);
+ return;
+ }
+ // 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];
+ }
+ }
+
+ /**
+ * Jumps back the current position of this MBB by specified length.
+ * @param length
+ */
+ public void moveBack(int length) {
+ if (this.singleItem) {
+ this.curItem.position(curItem.position() - length);
+ return;
+ }
+ 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;
+ }
+ }
+ }
+
+ /**
+ * Returns bytes from current position till length specified, as a single ByteButter. 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.
+ */
+ public ByteBuffer asSubBuffer(int length) {
+ if (this.singleItem || 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 ByteButter. 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
+ * @return a pair of bytes from current position till length specified, as a single ByteButter and
+ * offset in that Buffer where the bytes starts.
+ */
+ public Pair<ByteBuffer, Integer> asSubBuffer(int offset, int length) {
+ if (this.singleItem) {
+ return new Pair<ByteBuffer, Integer>(this.curItem, offset);
+ }
+ if (this.itemBeginPos[this.curItemIndex] <= offset) {
+ int relOffsetInCurItem = offset - this.itemBeginPos[this.curItemIndex];
+ if (this.curItem.limit() - relOffsetInCurItem >= length) {
+ return new Pair<ByteBuffer, Integer>(this.curItem, relOffsetInCurItem);
+ }
+ }
+ int itemIndex = getItemIndex(offset);
+ ByteBuffer item = this.items[itemIndex];
+ offset = offset - this.itemBeginPos[itemIndex];
+ if (item.limit() - offset >= length) {
+ return new Pair<ByteBuffer, Integer>(item, offset);
+ }
+ 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;
+ }
+ return new Pair<ByteBuffer, Integer>(ByteBuffer.wrap(dst), 0);
+ }
+
+ /**
+ * Compares two MBBs
+ *
+ * @param buf1 the first MBB
+ * @param o1 the offset in the first MBB from where the compare has to happen
+ * @param len1 the length in the first MBB upto which the compare has to happen
+ * @param buf2 the second MBB
+ * @param o2 the offset in the second MBB from where the compare has to happen
+ * @param len2 the length in the second MBB upto which the compare has to happen
+ * @return Positive if buf1 is bigger than buf2, 0 if they are equal, and negative if buf1 is
+ * smaller than buf2.
+ */
+ public static int compareTo(MultiByteBuffer buf1, int o1, int len1, MultiByteBuffer buf2, int o2,
+ int len2) {
+ if (buf1.hasArray() && buf2.hasArray()) {
+ return Bytes.compareTo(buf1.array(), buf1.arrayOffset() + o1, len1, buf2.array(),
+ buf2.arrayOffset() + o2, len2);
+ }
+ int end1 = o1 + len1;
+ int end2 = o2 + len2;
+ for (int i = o1, j = o2; i < end1 && j < end2; i++, j++) {
+ int a = buf1.get(i) & 0xFF;
+ int b = buf2.get(j) & 0xFF;
+ if (a != b) {
+ return a - b;
+ }
+ }
+ return len1 - len2;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof MultiByteBuffer)) return false;
+ if (this == obj) return true;
+ MultiByteBuffer that = (MultiByteBuffer) obj;
+ if (this.capacity() != that.capacity()) return false;
+ if (compareTo(this, 0, this.capacity(), that, 0, this.capacity()) == 0) return true;
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = 0;
+ for (ByteBuffer b : this.items) {
+ hash += b.hashCode();
+ }
+ return hash;
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/hbase/blob/8ae4b374/hbase-common/src/main/java/org/apache/hadoop/hbase/util/ByteBufferUtils.java
----------------------------------------------------------------------
diff --git a/hbase-common/src/main/java/org/apache/hadoop/hbase/util/ByteBufferUtils.java b/hbase-common/src/main/java/org/apache/hadoop/hbase/util/ByteBufferUtils.java
index 6f348bc..9d14117 100644
--- a/hbase-common/src/main/java/org/apache/hadoop/hbase/util/ByteBufferUtils.java
+++ b/hbase-common/src/main/java/org/apache/hadoop/hbase/util/ByteBufferUtils.java
@@ -28,6 +28,8 @@ import org.apache.hadoop.hbase.classification.InterfaceStability;
import org.apache.hadoop.io.IOUtils;
import org.apache.hadoop.io.WritableUtils;
+import sun.nio.ch.DirectBuffer;
+
/**
* Utility functions for working with byte buffers, such as reading/writing
* variable-length long numbers.
@@ -508,4 +510,157 @@ public final class ByteBufferUtils {
}
return len1 - len2;
}
+
+ /**
+ * Reads a short value at the given buffer's offset.
+ * @param buffer
+ * @param offset
+ * @return short value at offset
+ */
+ public static short toShort(ByteBuffer buffer, int offset) {
+ if (UnsafeAccess.isAvailable()) {
+ return toShortUnsafe(buffer, offset);
+ } else {
+ return buffer.getShort(offset);
+ }
+ }
+
+ private static short toShortUnsafe(ByteBuffer buf, long offset) {
+ short ret;
+ if (buf.isDirect()) {
+ ret = UnsafeAccess.theUnsafe.getShort(((DirectBuffer) buf).address() + offset);
+ } else {
+ ret = UnsafeAccess.theUnsafe.getShort(buf.array(),
+ UnsafeAccess.BYTE_ARRAY_BASE_OFFSET + buf.arrayOffset() + offset);
+ }
+ if (UnsafeAccess.littleEndian) {
+ return Short.reverseBytes(ret);
+ }
+ return ret;
+ }
+
+ /**
+ * Reads an int value at the given buffer's offset.
+ * @param buffer
+ * @param offset
+ * @return int value at offset
+ */
+ public static int toInt(ByteBuffer buffer, int offset) {
+ if (UnsafeAccess.isAvailable()) {
+ return toIntUnsafe(buffer, offset);
+ } else {
+ return buffer.getInt(offset);
+ }
+ }
+
+ private static int toIntUnsafe(ByteBuffer buf, long offset) {
+ int ret;
+ if (buf.isDirect()) {
+ ret = UnsafeAccess.theUnsafe.getInt(((DirectBuffer) buf).address() + offset);
+ } else {
+ ret = UnsafeAccess.theUnsafe.getInt(buf.array(),
+ UnsafeAccess.BYTE_ARRAY_BASE_OFFSET + buf.arrayOffset() + offset);
+ }
+ if (UnsafeAccess.littleEndian) {
+ return Integer.reverseBytes(ret);
+ }
+ return ret;
+ }
+
+ /**
+ * Reads a long value at the given buffer's offset.
+ * @param buffer
+ * @param offset
+ * @return long value at offset
+ */
+ public static long toLong(ByteBuffer buffer, int offset) {
+ if (UnsafeAccess.isAvailable()) {
+ return toLongUnsafe(buffer, offset);
+ } else {
+ return buffer.getLong(offset);
+ }
+ }
+
+ private static long toLongUnsafe(ByteBuffer buf, long offset) {
+ long ret;
+ if (buf.isDirect()) {
+ ret = UnsafeAccess.theUnsafe.getLong(((DirectBuffer) buf).address() + offset);
+ } else {
+ ret = UnsafeAccess.theUnsafe.getLong(buf.array(),
+ UnsafeAccess.BYTE_ARRAY_BASE_OFFSET + buf.arrayOffset() + offset);
+ }
+ if (UnsafeAccess.littleEndian) {
+ return Long.reverseBytes(ret);
+ }
+ return ret;
+ }
+
+ /**
+ * Copies the bytes from given array's offset to length part into the given buffer. Puts the bytes
+ * to buffer's current position. This also advances the position in the 'out' buffer by 'length'
+ * @param out
+ * @param in
+ * @param inOffset
+ * @param length
+ */
+ public static void copyFromArrayToBuffer(ByteBuffer out, byte[] in, int inOffset, int length) {
+ if (out.hasArray()) {
+ System.arraycopy(in, inOffset, out.array(), out.arrayOffset() + out.position(), length);
+ // Move the position in out by length
+ out.position(out.position() + length);
+ } else if (UnsafeAccess.isAvailable()) {
+ copyUnsafe(in, inOffset, out, out.position(), length);
+ // Move the position in out by length
+ out.position(out.position() + length);
+ } else {
+ out.put(in, inOffset, length);
+ }
+ }
+
+ static void copyUnsafe(byte[] src, int srcOffset, ByteBuffer dest, int destOffset, int length) {
+ long destAddress = destOffset;
+ Object destBase = null;
+ if (dest.isDirect()) {
+ destAddress = destAddress + ((DirectBuffer) dest).address();
+ } else {
+ destAddress = destAddress + UnsafeAccess.BYTE_ARRAY_BASE_OFFSET + dest.arrayOffset();
+ destBase = dest.array();
+ }
+ long srcAddress = srcOffset + UnsafeAccess.BYTE_ARRAY_BASE_OFFSET;
+ UnsafeAccess.theUnsafe.copyMemory(src, srcAddress, destBase, destAddress, length);
+ }
+
+ static void copyUnsafe(ByteBuffer src, int srcOffset, byte[] dest, int destOffset, int length) {
+ long srcAddress = srcOffset;
+ Object srcBase = null;
+ if (src.isDirect()) {
+ srcAddress = srcAddress + ((DirectBuffer) src).address();
+ } else {
+ srcAddress = srcAddress + UnsafeAccess.BYTE_ARRAY_BASE_OFFSET + src.arrayOffset();
+ srcBase = src.array();
+ }
+ long destAddress = destOffset + UnsafeAccess.BYTE_ARRAY_BASE_OFFSET;
+ UnsafeAccess.theUnsafe.copyMemory(srcBase, srcAddress, dest, destAddress, length);
+ }
+
+ /**
+ * Copies specified number of bytes from given offset of 'in' ByteBuffer to the array.
+ * @param out
+ * @param in
+ * @param sourceOffset
+ * @param destinationOffset
+ * @param length
+ */
+ public static void copyFromBufferToArray(byte[] out, ByteBuffer in,
+ int sourceOffset, int destinationOffset, int length) {
+ if (in.hasArray()) {
+ System.arraycopy(in.array(), sourceOffset + in.arrayOffset(), out, destinationOffset, length);
+ } else if (UnsafeAccess.isAvailable()) {
+ copyUnsafe(in, sourceOffset, out, destinationOffset, length);
+ } else {
+ for (int i = 0; i < length; i++) {
+ out[destinationOffset + i] = in.get(sourceOffset + i);
+ }
+ }
+ }
}
http://git-wip-us.apache.org/repos/asf/hbase/blob/8ae4b374/hbase-common/src/test/java/org/apache/hadoop/hbase/nio/TestMultiByteBuffer.java
----------------------------------------------------------------------
diff --git a/hbase-common/src/test/java/org/apache/hadoop/hbase/nio/TestMultiByteBuffer.java b/hbase-common/src/test/java/org/apache/hadoop/hbase/nio/TestMultiByteBuffer.java
new file mode 100644
index 0000000..27f3484
--- /dev/null
+++ b/hbase-common/src/test/java/org/apache/hadoop/hbase/nio/TestMultiByteBuffer.java
@@ -0,0 +1,280 @@
+/**
+ * Copyright The Apache Software Foundation
+ *
+ * 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 static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutput;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.nio.BufferOverflowException;
+import java.nio.ByteBuffer;
+
+import org.apache.hadoop.hbase.testclassification.MiscTests;
+import org.apache.hadoop.hbase.testclassification.SmallTests;
+import org.apache.hadoop.hbase.util.ByteBufferUtils;
+import org.apache.hadoop.hbase.util.Bytes;
+import org.apache.hadoop.hbase.util.Pair;
+import org.apache.hadoop.io.WritableUtils;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+
+@Category({ MiscTests.class, SmallTests.class })
+public class TestMultiByteBuffer {
+
+ @Test
+ public void testWritesAndReads() {
+ // Absolute reads
+ ByteBuffer bb1 = ByteBuffer.allocate(15);
+ ByteBuffer bb2 = ByteBuffer.allocate(15);
+ int i1 = 4;
+ bb1.putInt(i1);
+ long l1 = 45L, l2 = 100L, l3 = 12345L;
+ bb1.putLong(l1);
+ short s1 = 2;
+ bb1.putShort(s1);
+ byte[] b = Bytes.toBytes(l2);
+ bb1.put(b, 0, 1);
+ bb2.put(b, 1, 7);
+ bb2.putLong(l3);
+ MultiByteBuffer mbb = new MultiByteBuffer(bb1, bb2);
+ assertEquals(l1, mbb.getLong(4));
+ assertEquals(l2, mbb.getLong(14));
+ assertEquals(l3, mbb.getLong(22));
+ assertEquals(i1, mbb.getInt(0));
+ assertEquals(s1, mbb.getShort(12));
+ // Relative reads
+ assertEquals(i1, mbb.getInt());
+ assertEquals(l1, mbb.getLong());
+ assertEquals(s1, mbb.getShort());
+ assertEquals(l2, mbb.getLong());
+ assertEquals(l3, mbb.getLong());
+ // Absolute writes
+ bb1 = ByteBuffer.allocate(15);
+ bb2 = ByteBuffer.allocate(15);
+ mbb = new MultiByteBuffer(bb1, bb2);
+ byte b1 = 5, b2 = 31;
+ mbb.put(b1);
+ mbb.putLong(l1);
+ mbb.putInt(i1);
+ mbb.putLong(l2);
+ mbb.put(b2);
+ mbb.position(mbb.position() + 2);
+ try {
+ mbb.putLong(l3);
+ fail("'Should have thrown BufferOverflowException");
+ } catch (BufferOverflowException e) {
+ }
+ mbb.position(mbb.position() - 2);
+ mbb.putLong(l3);
+ mbb.rewind();
+ assertEquals(b1, mbb.get());
+ assertEquals(l1, mbb.getLong());
+ assertEquals(i1, mbb.getInt());
+ assertEquals(l2, mbb.getLong());
+ assertEquals(b2, mbb.get());
+ assertEquals(l3, mbb.getLong());
+ mbb.put(21, b1);
+ mbb.position(21);
+ assertEquals(b1, mbb.get());
+ mbb.put(b);
+ assertEquals(l2, mbb.getLong(22));
+ }
+
+ @Test
+ public void testGetVlong() throws IOException {
+ long vlong = 453478;
+ ByteArrayOutputStream baos = new ByteArrayOutputStream(10);
+ DataOutput out = new DataOutputStream(baos);
+ WritableUtils.writeVLong(out, vlong);
+ ByteBuffer bb = ByteBuffer.wrap(baos.toByteArray());
+ MultiByteBuffer mbb = new MultiByteBuffer(bb);
+ assertEquals(vlong, mbb.getVLong());
+ }
+
+ @Test
+ public void testArrayBasedMethods() {
+ byte[] b = new byte[15];
+ ByteBuffer bb1 = ByteBuffer.wrap(b, 1, 10).slice();
+ ByteBuffer bb2 = ByteBuffer.allocate(15);
+ MultiByteBuffer mbb1 = new MultiByteBuffer(bb1, bb2);
+ assertFalse(mbb1.hasArray());
+ try {
+ mbb1.array();
+ fail();
+ } catch (UnsupportedOperationException e) {
+ }
+ try {
+ mbb1.arrayOffset();
+ fail();
+ } catch (UnsupportedOperationException e) {
+ }
+ mbb1 = new MultiByteBuffer(bb1);
+ assertTrue(mbb1.hasArray());
+ assertEquals(1, mbb1.arrayOffset());
+ assertEquals(b, mbb1.array());
+ mbb1 = new MultiByteBuffer(ByteBuffer.allocateDirect(10));
+ assertFalse(mbb1.hasArray());
+ try {
+ mbb1.array();
+ fail();
+ } catch (UnsupportedOperationException e) {
+ }
+ try {
+ mbb1.arrayOffset();
+ fail();
+ } catch (UnsupportedOperationException e) {
+ }
+ }
+
+ @Test
+ public void testMarkAndReset() {
+ ByteBuffer bb1 = ByteBuffer.allocateDirect(15);
+ ByteBuffer bb2 = ByteBuffer.allocateDirect(15);
+ bb1.putInt(4);
+ long l1 = 45L, l2 = 100L, l3 = 12345L;
+ bb1.putLong(l1);
+ bb1.putShort((short) 2);
+ byte[] b = Bytes.toBytes(l2);
+ bb1.put(b, 0, 1);
+ bb2.put(b, 1, 7);
+ bb2.putLong(l3);
+ MultiByteBuffer multi = new MultiByteBuffer(bb1, bb2);
+ assertEquals(4, multi.getInt());
+ assertEquals(l1, multi.getLong());
+ multi.mark();
+ assertEquals((short) 2, multi.getShort());
+ multi.reset();
+ assertEquals((short) 2, multi.getShort());
+ multi.mark();
+ assertEquals(l2, multi.getLong());
+ multi.reset();
+ assertEquals(l2, multi.getLong());
+ multi.mark();
+ assertEquals(l3, multi.getLong());
+ multi.reset();
+ assertEquals(l3, multi.getLong());
+ // Try absolute gets with mark and reset
+ multi.mark();
+ assertEquals(l2, multi.getLong(14));
+ multi.reset();
+ assertEquals(l3, multi.getLong(22));
+ // Just reset to see what happens
+ multi.reset();
+ assertEquals(l2, multi.getLong(14));
+ multi.mark();
+ assertEquals(l3, multi.getLong(22));
+ multi.reset();
+ }
+
+ @Test
+ public void testSkipNBytes() {
+ ByteBuffer bb1 = ByteBuffer.allocate(15);
+ ByteBuffer bb2 = ByteBuffer.allocate(15);
+ bb1.putInt(4);
+ long l1 = 45L, l2 = 100L, l3 = 12345L;
+ bb1.putLong(l1);
+ bb1.putShort((short) 2);
+ byte[] b = Bytes.toBytes(l2);
+ bb1.put(b, 0, 1);
+ bb2.put(b, 1, 7);
+ bb2.putLong(l3);
+ MultiByteBuffer multi = new MultiByteBuffer(bb1, bb2);
+ assertEquals(4, multi.getInt());
+ assertEquals(l1, multi.getLong());
+ multi.skip(10);
+ assertEquals(l3, multi.getLong());
+ }
+
+ @Test
+ public void testMoveBack() {
+ ByteBuffer bb1 = ByteBuffer.allocate(15);
+ ByteBuffer bb2 = ByteBuffer.allocate(15);
+ bb1.putInt(4);
+ long l1 = 45L, l2 = 100L, l3 = 12345L;
+ bb1.putLong(l1);
+ bb1.putShort((short) 2);
+ byte[] b = Bytes.toBytes(l2);
+ bb1.put(b, 0, 1);
+ bb2.put(b, 1, 7);
+ bb2.putLong(l3);
+ MultiByteBuffer multi = new MultiByteBuffer(bb1, bb2);
+ assertEquals(4, multi.getInt());
+ assertEquals(l1, multi.getLong());
+ multi.skip(10);
+ multi.moveBack(4);
+ multi.moveBack(6);
+ multi.moveBack(8);
+ assertEquals(l1, multi.getLong());
+ }
+
+ @Test
+ public void testSubBuffer() {
+ ByteBuffer bb1 = ByteBuffer.allocateDirect(10);
+ ByteBuffer bb2 = ByteBuffer.allocateDirect(10);
+ MultiByteBuffer multi = new MultiByteBuffer(bb1, bb2);
+ long l1 = 1234L, l2 = 100L;
+ multi.putLong(l1);
+ multi.putLong(l2);
+ multi.rewind();
+ ByteBuffer sub = multi.asSubBuffer(Bytes.SIZEOF_LONG);
+ assertTrue(bb1 == sub);
+ assertEquals(l1, ByteBufferUtils.toLong(sub, sub.position()));
+ multi.skip(Bytes.SIZEOF_LONG);
+ sub = multi.asSubBuffer(Bytes.SIZEOF_LONG);
+ assertFalse(bb1 == sub);
+ assertFalse(bb2 == sub);
+ assertEquals(l2, ByteBufferUtils.toLong(sub, sub.position()));
+ multi.rewind();
+ Pair<ByteBuffer, Integer> p = multi.asSubBuffer(8, Bytes.SIZEOF_LONG);
+ assertFalse(bb1 == p.getFirst());
+ assertFalse(bb2 == p.getFirst());
+ assertEquals(0, p.getSecond().intValue());
+ assertEquals(l2, ByteBufferUtils.toLong(sub, p.getSecond()));
+ }
+
+ @Test
+ public void testSliceDuplicateMethods() throws Exception {
+ ByteBuffer bb1 = ByteBuffer.allocateDirect(10);
+ ByteBuffer bb2 = ByteBuffer.allocateDirect(15);
+ MultiByteBuffer multi = new MultiByteBuffer(bb1, bb2);
+ long l1 = 1234L, l2 = 100L;
+ multi.put((byte) 2);
+ multi.putLong(l1);
+ multi.putLong(l2);
+ multi.putInt(45);
+ multi.position(1);
+ multi.limit(multi.position() + (2 * Bytes.SIZEOF_LONG));
+ MultiByteBuffer sliced = multi.slice();
+ assertEquals(0, sliced.position());
+ assertEquals((2 * Bytes.SIZEOF_LONG), sliced.limit());
+ assertEquals(l1, sliced.getLong());
+ assertEquals(l2, sliced.getLong());
+ MultiByteBuffer dup = multi.duplicate();
+ assertEquals(1, dup.position());
+ assertEquals(dup.position() + (2 * Bytes.SIZEOF_LONG), dup.limit());
+ assertEquals(l1, dup.getLong());
+ assertEquals(l2, dup.getLong());
+ }
+}
http://git-wip-us.apache.org/repos/asf/hbase/blob/8ae4b374/hbase-server/src/test/java/org/apache/hadoop/hbase/util/TestByteBufferUtils.java
----------------------------------------------------------------------
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/util/TestByteBufferUtils.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/util/TestByteBufferUtils.java
index 8a48d32..974b0d2 100644
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/util/TestByteBufferUtils.java
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/util/TestByteBufferUtils.java
@@ -323,4 +323,53 @@ public class TestByteBufferUtils {
assertEquals(5, buffer.position());
assertEquals(5, buffer.limit());
}
+
+ @Test
+ public void testToPrimitiveTypes() {
+ ByteBuffer buffer = ByteBuffer.allocate(15);
+ long l = 988L;
+ int i = 135;
+ short s = 7;
+ buffer.putLong(l);
+ buffer.putShort(s);
+ buffer.putInt(i);
+ assertEquals(l, ByteBufferUtils.toLong(buffer, 0));
+ assertEquals(s, ByteBufferUtils.toShort(buffer, 8));
+ assertEquals(i, ByteBufferUtils.toInt(buffer, 10));
+ }
+
+ @Test
+ public void testCopyFromArrayToBuffer() {
+ byte[] b = new byte[15];
+ b[0] = -1;
+ long l = 988L;
+ int i = 135;
+ short s = 7;
+ Bytes.putLong(b, 1, l);
+ Bytes.putShort(b, 9, s);
+ Bytes.putInt(b, 11, i);
+ ByteBuffer buffer = ByteBuffer.allocate(14);
+ ByteBufferUtils.copyFromArrayToBuffer(buffer, b, 1, 14);
+ buffer.rewind();
+ assertEquals(l, buffer.getLong());
+ assertEquals(s, buffer.getShort());
+ assertEquals(i, buffer.getInt());
+ }
+
+ @Test
+ public void testCopyFromBufferToArray() {
+ ByteBuffer buffer = ByteBuffer.allocate(15);
+ buffer.put((byte) -1);
+ long l = 988L;
+ int i = 135;
+ short s = 7;
+ buffer.putShort(s);
+ buffer.putInt(i);
+ buffer.putLong(l);
+ byte[] b = new byte[15];
+ ByteBufferUtils.copyFromBufferToArray(b, buffer, 1, 1, 14);
+ assertEquals(s, Bytes.toShort(b, 1));
+ assertEquals(i, Bytes.toInt(b, 3));
+ assertEquals(l, Bytes.toLong(b, 7));
+ }
}