From 84bd0d3b4659f87ade7aaab81b3ec75c048e7339 Mon Sep 17 00:00:00 2001
From: Nick Dimiduk
+ * {@code ByteRange} maintains an underlying byte[] and a viewport into that
+ * byte[] as a range of bytes. The {@code ByteRange} is a mutable, reusable
+ * object, so the underlying byte[] can be modified after instantiation. This
+ * is done using the {@link #set(byte[])} and {@link #unset()} methods. Direct
+ * access to the byte[] is also available via {@link #getBytes()}. The viewport
+ * is defined by an {@code offset} into the byte[] and a {@code length}. The
+ * range of bytes is 0-indexed, and is accessed by index via the
+ * {@link #get(int)} and {@link #put(int, byte)} methods.
+ *
+ * This interface differs from ByteBuffer:
+ *
+ * Mutable, and always evaluates {@code #equals(Object)}, {@code #hashCode()}, + * and {@code #compareTo(ByteRange)} based on the current contents. + *
+ *+ * Can contain convenience methods for comparing, printing, cloning, spawning + * new arrays, copying to other arrays, etc. Please place non-core methods into + * {@link ByteRangeUtils}. + *
*/ -public class ByteRange implements Comparable+ * Extends {@link ByteRange} with additional methods to support tracking a + * consumers position within the viewport. The API is extended with methods + * {@link #get()} and {@link #put(byte)} for interacting with the backing + * array from the current position forward. This frees the caller from managing + * their own index into the array. + *
+ *+ * Designed to be a slimmed-down, mutable alternative to {@link ByteBuffer}. + *
+ */ +@InterfaceAudience.Public +@InterfaceStability.Evolving +public interface PositionedByteRange extends ByteRange { + + // net new API is here. + + /** + * The current {@code position} marker. This valuae is 0-indexed, relative to + * the beginning of the range. + */ + public int getPosition(); + + /** + * Update the {@code position} index. May not be greater than {@code length}. + * @param position the new position in this range. + * @return this. + */ + public PositionedByteRange setPosition(int position); + + /** + * The number of bytes remaining between position and the end of the range. + */ + public int getRemaining(); + + /** + * Retrieve the next byte from this range without incrementing position. + */ + public byte peek(); + + /** + * Retrieve the next byte from this range. + */ + public byte get(); + + /** + * Fill {@code dst} with bytes from the range, starting from {@code position}. + * This range's {@code position} is incremented by the length of {@code dst}, + * the number of bytes copied. + * @param dst the destination of the copy. + * @return this. + */ + public PositionedByteRange get(byte[] dst); + + /** + * Fill {@code dst} with bytes from the range, starting from the current + * {@code position}. {@code length} bytes are copied into {@code dst}, + * starting at {@code offset}. This range's {@code position} is incremented + * by the number of bytes copied. + * @param dst the destination of the copy. + * @param offset the offset into {@code dst} to start the copy. + * @param length the number of bytes to copy into {@code dst}. + * @return this. + */ + public PositionedByteRange get(byte[] dst, int offset, int length); + + /** + * Store {@code val} at the next position in this range. + * @param val the new value. + * @return this. + */ + public PositionedByteRange put(byte val); + + /** + * Store the content of {@code val} in this range, starting at the next position. + * @param val the new value. + * @return this. + */ + public PositionedByteRange put(byte[] val); + + /** + * Store {@code length} bytes from {@code val} into this range. Bytes from + * {@code val} are copied starting at {@code offset} into the range, starting at + * the current position. + * @param val the new value. + * @param offset the offset in {@code val} from which to start copying. + * @param length the number of bytes to copy from {@code val}. + * @return this. + */ + public PositionedByteRange put(byte[] val, int offset, int length); + + // override parent interface declarations to return this interface. + + @Override + public PositionedByteRange unset(); + + @Override + public PositionedByteRange set(int capacity); + + @Override + public PositionedByteRange set(byte[] bytes); + + @Override + public PositionedByteRange set(byte[] bytes, int offset, int length); + + @Override + public PositionedByteRange setOffset(int offset); + + @Override + public PositionedByteRange setLength(int length); + + @Override + public PositionedByteRange get(int index, byte[] dst); + + @Override + public PositionedByteRange get(int index, byte[] dst, int offset, int length); + + @Override + public PositionedByteRange put(int index, byte val); + + @Override + public PositionedByteRange put(int index, byte[] val); + + @Override + public PositionedByteRange put(int index, byte[] val, int offset, int length); + + @Override + public PositionedByteRange deepCopy(); + + @Override + public PositionedByteRange shallowCopy(); + + @Override + public PositionedByteRange shallowCopySubRange(int innerOffset, int copyLength); +} diff --git a/hbase-common/src/main/java/org/apache/hadoop/hbase/util/SimpleByteRange.java b/hbase-common/src/main/java/org/apache/hadoop/hbase/util/SimpleByteRange.java new file mode 100644 index 0000000..6f71c66 --- /dev/null +++ b/hbase-common/src/main/java/org/apache/hadoop/hbase/util/SimpleByteRange.java @@ -0,0 +1,325 @@ +/* + * 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.util; + +import org.apache.hadoop.classification.InterfaceAudience; +import org.apache.hadoop.classification.InterfaceStability; + +/** + * A basic {@link ByteRange} implementation. + */ +@InterfaceAudience.Public +@InterfaceStability.Evolving +public class SimpleByteRange implements ByteRange { + + private static final int UNSET_HASH_VALUE = -1; + + // Note to maintainers: Do not make these final, as the intention is to + // reuse objects of this class + + /** + * The array containing the bytes in this range. It will be >= length. + */ + protected byte[] bytes; + + /** + * The index of the first byte in this range. {@code ByteRange.get(0)} will + * return bytes[offset]. + */ + protected int offset; + + /** + * The number of bytes in the range. Offset + length must be <= bytes.length + */ + protected int length; + + /** + * Variable for lazy-caching the hashCode of this range. Useful for + * frequently used ranges, long-lived ranges, or long ranges. + */ + private int hash = UNSET_HASH_VALUE; + + /** + * Create a new {@code ByteRange} lacking a backing array and with an + * undefined viewport. + */ + public SimpleByteRange() { + unset(); + } + + /** + * Create a new {@code ByteRange} over a new backing array of size + * {@code capacity}. The range's offset and length are 0 and {@code capacity}, + * respectively. + * @param capacity the size of the backing array. + */ + public SimpleByteRange(int capacity) { + this(new byte[capacity]); + } + + /** + * Create a new {@code ByteRange} over the provided {@code bytes}. + * @param bytes The array to wrap. + */ + public SimpleByteRange(byte[] bytes) { + set(bytes); + } + + /** + * Create a new {@code ByteRange} over the provided {@code bytes}. + * @param bytes The array to wrap. + * @param offset The offset into {@code bytes} considered the beginning + * of this range. + * @param length The length of this range. + */ + public SimpleByteRange(byte[] bytes, int offset, int length) { + set(bytes, offset, length); + } + + // + // methods for managing the backing array and range viewport + // + + @Override + public byte[] getBytes() { + return bytes; + } + + @Override + public ByteRange unset() { + clearHashCache(); + this.bytes = null; + this.offset = 0; + this.length = 0; + return this; + } + + @Override + public ByteRange set(int capacity) { + return set(new byte[capacity]); + } + + @Override + public ByteRange set(byte[] bytes) { + if (null == bytes) return unset(); + clearHashCache(); + this.bytes = bytes; + this.offset = 0; + this.length = bytes.length; + return this; + } + + @Override + public ByteRange set(byte[] bytes, int offset, int length) { + if (null == bytes) return unset(); + clearHashCache(); + this.bytes = bytes; + this.offset = offset; + this.length = length; + return this; + } + + @Override + public int getOffset() { + return offset; + } + + @Override + public ByteRange setOffset(int offset) { + clearHashCache(); + this.offset = offset; + return this; + } + + @Override + public int getLength() { + return length; + } + + @Override + public ByteRange setLength(int length) { + clearHashCache(); + this.length = length; + return this; + } + + @Override + public boolean isEmpty() { + return isEmpty(this); + } + + /** + * @return true when {@code range} is of zero length, false otherwise. + */ + public static boolean isEmpty(ByteRange range) { + return range == null || range.getLength() == 0; + } + + // + // methods for retrieving data + // + + @Override + public byte get(int index) { + return bytes[offset + index]; + } + + @Override + public ByteRange get(int index, byte[] dst) { + if (0 == dst.length) return this; + return get(index, dst, 0, dst.length); + } + + @Override + public ByteRange get(int index, byte[] dst, int offset, int length) { + if (0 == length) return this; + System.arraycopy(this.bytes, this.offset + index, dst, offset, length); + return this; + } + + @Override + public ByteRange put(int index, byte val) { + bytes[offset + index] = val; + return this; + } + + @Override + public ByteRange put(int index, byte[] val) { + if (0 == val.length) return this; + return put(index, val, 0, val.length); + } + + @Override + public ByteRange put(int index, byte[] val, int offset, int length) { + if (0 == length) return this; + System.arraycopy(val, offset, this.bytes, this.offset + index, length); + return this; + } + + // + // methods for duplicating the current instance + // + + @Override + public byte[] deepCopyToNewArray() { + byte[] result = new byte[length]; + System.arraycopy(bytes, offset, result, 0, length); + return result; + } + + @Override + public ByteRange deepCopy() { + SimpleByteRange clone = new SimpleByteRange(deepCopyToNewArray()); + if (isHashCached()) { + clone.hash = hash; + } + return clone; + } + + @Override + public void deepCopyTo(byte[] destination, int destinationOffset) { + System.arraycopy(bytes, offset, destination, destinationOffset, length); + } + + @Override + public void deepCopySubRangeTo(int innerOffset, int copyLength, byte[] destination, + int destinationOffset) { + System.arraycopy(bytes, offset + innerOffset, destination, destinationOffset, copyLength); + } + + @Override + public ByteRange shallowCopy() { + SimpleByteRange clone = new SimpleByteRange(bytes, offset, length); + if (isHashCached()) { + clone.hash = hash; + } + return clone; + } + + @Override + public ByteRange shallowCopySubRange(int innerOffset, int copyLength) { + SimpleByteRange clone = new SimpleByteRange(bytes, offset + innerOffset, copyLength); + if (isHashCached()) { + clone.hash = hash; + } + return clone; + } + + // + // methods used for comparison + // + + @Override + public boolean equals(Object thatObject) { + if (thatObject == null){ + return false; + } + if (this == thatObject) { + return true; + } + if (hashCode() != thatObject.hashCode()) { + return false; + } + if (!(thatObject instanceof SimpleByteRange)) { + return false; + } + SimpleByteRange that = (SimpleByteRange) thatObject; + return Bytes.equals(bytes, offset, length, that.bytes, that.offset, that.length); + } + + @Override + public int hashCode() { + if (isHashCached()) {// hash is already calculated and cached + return hash; + } + if (this.isEmpty()) {// return 0 for empty ByteRange + hash = 0; + return hash; + } + int off = offset; + hash = 0; + for (int i = 0; i < length; i++) { + hash = 31 * hash + bytes[off++]; + } + return hash; + } + + private boolean isHashCached() { + return hash != UNSET_HASH_VALUE; + } + + protected void clearHashCache() { + hash = UNSET_HASH_VALUE; + } + + /** + * Bitwise comparison of each byte in the array. Unsigned comparison, not + * paying attention to java's signed bytes. + */ + @Override + public int compareTo(ByteRange other) { + return Bytes.compareTo(bytes, offset, length, other.getBytes(), other.getOffset(), + other.getLength()); + } + + @Override + public String toString() { + return Bytes.toStringBinary(bytes, offset, length); + } +} diff --git a/hbase-common/src/main/java/org/apache/hadoop/hbase/util/SimplePositionedByteRange.java b/hbase-common/src/main/java/org/apache/hadoop/hbase/util/SimplePositionedByteRange.java new file mode 100644 index 0000000..8a61548 --- /dev/null +++ b/hbase-common/src/main/java/org/apache/hadoop/hbase/util/SimplePositionedByteRange.java @@ -0,0 +1,259 @@ +/* + * 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.util; + +import java.nio.ByteBuffer; + +import org.apache.hadoop.classification.InterfaceAudience; +import org.apache.hadoop.classification.InterfaceStability; + +import com.google.common.annotations.VisibleForTesting; + +/** + * Extends the basic {@link SimpleByteRange} implementation with position + * support. {@code position} is considered transient, not fundamental to the + * definition of the range, and does not participate in comparison or copy + * operations. + */ +@InterfaceAudience.Public +@InterfaceStability.Evolving +public class SimplePositionedByteRange extends SimpleByteRange implements PositionedByteRange { + + /** + * The current index into the range. Like {@link ByteBuffer} position, it + * points to the next value that will be read/written in the array. It + * provides the appearance of being 0-indexed, even though its value is + * calculated according to offset. + *+ * Position is considered transient and does not participate in + * {@link #equals(Object)} or {@link #hashCode()} comparisons. + *
+ */ + private int position = 0; + + /** + * Create a new {@code PositionedByteRange} lacking a backing array and with + * an undefined viewport. + */ + public SimplePositionedByteRange() { + super(); + } + + /** + * Create a new {@code PositionedByteRange} over a new backing array of + * size {@code capacity}. The range's offset and length are 0 and + * {@code capacity}, respectively. + * @param capacity the size of the backing array. + */ + public SimplePositionedByteRange(int capacity) { + super(capacity); + } + + /** + * Create a new {@code PositionedByteRange} over the provided {@code bytes}. + * @param bytes The array to wrap. + */ + public SimplePositionedByteRange(byte[] bytes) { + super(bytes); + } + + /** + * Create a new {@code PositionedByteRange} over the provided {@code bytes}. + * @param bytes The array to wrap. + * @param offset The offset into {@code bytes} considered the beginning + * of this range. + * @param length The length of this range. + */ + public SimplePositionedByteRange(byte[] bytes, int offset, int length) { + super(bytes, offset, length); + } + + @Override + public PositionedByteRange unset() { + this.position = 0; + super.unset(); + return this; + } + + @Override + public PositionedByteRange set(int capacity) { + this.position = 0; + super.set(capacity); + return this; + } + + @Override + public PositionedByteRange set(byte[] bytes) { + this.position = 0; + super.set(bytes); + return this; + } + + @Override + public PositionedByteRange set(byte[] bytes, int offset, int length) { + this.position = 0; + super.set(bytes, offset, length); + return this; + } + + /** + * Update the beginning of this range. {@code offset + length} may not be greater than + * {@code bytes.length}. Resets {@code position} to 0. + * @param offset the new start of this range. + * @return this. + */ + @Override + public PositionedByteRange setOffset(int offset) { + this.position = 0; + super.setOffset(offset); + return this; + } + + /** + * Update the length of this range. {@code offset + length} should not be + * greater than {@code bytes.length}. If {@code position} is greater than + * the new {@code length}, sets {@code position} to {@code length}. + * @param length The new length of this range. + * @return this. + */ + @Override + public PositionedByteRange setLength(int length) { + this.position = Math.min(position, length); + super.setLength(length); + return this; + } + + @Override + public int getPosition() { return position; } + + @Override + public PositionedByteRange setPosition(int position) { this.position = position; return this; } + + @Override + public int getRemaining() { return length - position; } + + @Override + public byte peek() { return bytes[offset + position]; } + + @Override + public byte get() { return get(position++); } + + @Override + public PositionedByteRange get(byte[] dst) { + if (0 == dst.length) return this; + return this.get(dst, 0, dst.length); // be clear we're calling self, not super + } + + @Override + public PositionedByteRange get(byte[] dst, int offset, int length) { + if (0 == length) return this; + super.get(this.position, dst, offset, length); + this.position += length; + return this; + } + + @Override + public PositionedByteRange put(byte val) { + put(position++, val); + return this; + } + + @Override + public PositionedByteRange put(byte[] val) { + if (0 == val.length) return this; + return this.put(val, 0, val.length); + } + + @Override + public PositionedByteRange put(byte[] val, int offset, int length) { + if (0 == length) return this; + super.put(position, val, offset, length); + this.position += length; + return this; + } + + /** + * Similar to {@link ByteBuffer#flip()}. Sets length to position, position + * to offset. + */ + @VisibleForTesting + PositionedByteRange flip() { + clearHashCache(); + length = position; + position = offset; + return this; + } + + /** + * Similar to {@link ByteBuffer#clear()}. Sets position to 0, length to + * capacity. + */ + @VisibleForTesting + PositionedByteRange clear() { + clearHashCache(); + position = 0; + length = bytes.length - offset; + return this; + } + + // java boilerplate + + @Override + public PositionedByteRange get(int index, byte[] dst) { super.get(index, dst); return this; } + + @Override + public PositionedByteRange get(int index, byte[] dst, int offset, int length) { + super.get(index, dst, offset, length); + return this; + } + + @Override + public PositionedByteRange put(int index, byte val) { super.put(index, val); return this; } + + @Override + public PositionedByteRange put(int index, byte[] val) { super.put(index, val); return this; } + + @Override + public PositionedByteRange put(int index, byte[] val, int offset, int length) { + super.put(index, val, offset, length); + return this; + } + + @Override + public PositionedByteRange deepCopy() { + SimplePositionedByteRange clone = new SimplePositionedByteRange(deepCopyToNewArray()); + clone.position = this.position; + return clone; + } + + @Override + public PositionedByteRange shallowCopy() { + SimplePositionedByteRange clone = new SimplePositionedByteRange(bytes, offset, length); + clone.position = this.position; + return clone; + } + + @Override + public PositionedByteRange shallowCopySubRange(int innerOffset, int copyLength) { + SimplePositionedByteRange clone = + new SimplePositionedByteRange(bytes, offset + innerOffset, copyLength); + clone.position = this.position; + return clone; + } +} diff --git a/hbase-common/src/test/java/org/apache/hadoop/hbase/util/TestByteRange.java b/hbase-common/src/test/java/org/apache/hadoop/hbase/util/TestByteRange.java deleted file mode 100644 index 5b50cb8..0000000 --- a/hbase-common/src/test/java/org/apache/hadoop/hbase/util/TestByteRange.java +++ /dev/null @@ -1,75 +0,0 @@ -/** - * 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.util; - -import junit.framework.Assert; -import junit.framework.TestCase; - -import org.apache.hadoop.hbase.SmallTests; -import org.junit.experimental.categories.Category; - - -@Category(SmallTests.class) -public class TestByteRange extends TestCase { - - public void testEmpty(){ - Assert.assertTrue(ByteRange.isEmpty(null)); - ByteRange r = new ByteRange(); - Assert.assertTrue(ByteRange.isEmpty(r)); - Assert.assertFalse(ByteRange.notEmpty(r)); - Assert.assertTrue(r.isEmpty()); - Assert.assertFalse(r.notEmpty()); - Assert.assertNotNull(r.getBytes());//should be empty byte[], but could change this behavior - Assert.assertEquals(0, r.getBytes().length); - Assert.assertEquals(0, r.getOffset()); - Assert.assertEquals(0, r.getLength()); - Assert.assertTrue(Bytes.equals(new byte[0], r.deepCopyToNewArray())); - Assert.assertEquals(0, r.compareTo(new ByteRange(new byte[0], 0, 0))); - Assert.assertEquals(0, r.hashCode()); - } - - public void testBasics(){ - ByteRange r = new ByteRange(new byte[]{1, 3, 2}); - Assert.assertFalse(ByteRange.isEmpty(r)); - Assert.assertNotNull(r.getBytes());//should be empty byte[], but could change this behavior - Assert.assertEquals(3, r.getBytes().length); - Assert.assertEquals(0, r.getOffset()); - Assert.assertEquals(3, r.getLength()); - - //cloning (deep copying) - Assert.assertTrue(Bytes.equals(new byte[]{1, 3, 2}, r.deepCopyToNewArray())); - Assert.assertNotSame(r.getBytes(), r.deepCopyToNewArray()); - - //hash code - Assert.assertTrue(r.hashCode() > 0); - Assert.assertEquals(r.hashCode(), r.deepCopy().hashCode()); - - //copying to arrays - byte[] destination = new byte[]{-59};//junk - r.deepCopySubRangeTo(2, 1, destination, 0); - Assert.assertTrue(Bytes.equals(new byte[]{2}, destination)); - - //set length - r.setLength(1); - Assert.assertTrue(Bytes.equals(new byte[]{1}, r.deepCopyToNewArray())); - r.setLength(2);//verify we retained the 2nd byte, but dangerous in real code - Assert.assertTrue(Bytes.equals(new byte[]{1, 3}, r.deepCopyToNewArray())); - } - -} - diff --git a/hbase-common/src/test/java/org/apache/hadoop/hbase/util/TestPositionedByteRange.java b/hbase-common/src/test/java/org/apache/hadoop/hbase/util/TestPositionedByteRange.java new file mode 100644 index 0000000..422ea78 --- /dev/null +++ b/hbase-common/src/test/java/org/apache/hadoop/hbase/util/TestPositionedByteRange.java @@ -0,0 +1,68 @@ +/** + * 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.util; + +import org.apache.hadoop.hbase.SmallTests; +import org.junit.Assert; +import org.junit.Test; +import org.junit.experimental.categories.Category; + +@Category(SmallTests.class) +public class TestPositionedByteRange { + @Test + public void testPosition() { + PositionedByteRange r = new SimplePositionedByteRange(new byte[5], 1, 3); + + // exercise single-byte put + r.put(Bytes.toBytes("f")[0]) + .put(Bytes.toBytes("o")[0]) + .put(Bytes.toBytes("o")[0]); + Assert.assertEquals(3, r.getPosition()); + Assert.assertArrayEquals( + new byte[] { 0, Bytes.toBytes("f")[0], Bytes.toBytes("o")[0], Bytes.toBytes("o")[0], 0 }, + r.getBytes()); + + // exercise multi-byte put + r.setPosition(0); + r.put(Bytes.toBytes("f")) + .put(Bytes.toBytes("o")) + .put(Bytes.toBytes("o")); + Assert.assertEquals(3, r.getPosition()); + Assert.assertArrayEquals( + new byte[] { 0, Bytes.toBytes("f")[0], Bytes.toBytes("o")[0], Bytes.toBytes("o")[0], 0 }, + r.getBytes()); + + // exercise single-byte get + r.setPosition(0); + Assert.assertEquals(Bytes.toBytes("f")[0], r.get()); + Assert.assertEquals(Bytes.toBytes("o")[0], r.get()); + Assert.assertEquals(Bytes.toBytes("o")[0], r.get()); + + r.setPosition(1); + Assert.assertEquals(Bytes.toBytes("o")[0], r.get()); + + // exercise multi-byte get + r.setPosition(0); + byte[] dst = new byte[3]; + r.get(dst); + Assert.assertArrayEquals(Bytes.toBytes("foo"), dst); + + // set position to the end of the range; this should not throw. + r.setPosition(3); + } +} diff --git a/hbase-common/src/test/java/org/apache/hadoop/hbase/util/TestSimpleByteRange.java b/hbase-common/src/test/java/org/apache/hadoop/hbase/util/TestSimpleByteRange.java new file mode 100644 index 0000000..4ee4335 --- /dev/null +++ b/hbase-common/src/test/java/org/apache/hadoop/hbase/util/TestSimpleByteRange.java @@ -0,0 +1,71 @@ +/** + * 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.util; + +import org.apache.hadoop.hbase.SmallTests; +import org.junit.Assert; +import org.junit.Test; +import org.junit.experimental.categories.Category; + +@Category(SmallTests.class) +public class TestSimpleByteRange { + + @Test + public void testEmpty(){ + Assert.assertTrue(SimpleByteRange.isEmpty(null)); + ByteRange r = new SimpleByteRange(); + Assert.assertTrue(SimpleByteRange.isEmpty(r)); + Assert.assertTrue(r.isEmpty()); + r.set(new byte[0]); + Assert.assertEquals(0, r.getBytes().length); + Assert.assertEquals(0, r.getOffset()); + Assert.assertEquals(0, r.getLength()); + Assert.assertTrue(Bytes.equals(new byte[0], r.deepCopyToNewArray())); + Assert.assertEquals(0, r.compareTo(new SimpleByteRange(new byte[0], 0, 0))); + Assert.assertEquals(0, r.hashCode()); + } + + @Test + public void testBasics() { + ByteRange r = new SimpleByteRange(new byte[] { 1, 3, 2 }); + Assert.assertFalse(SimpleByteRange.isEmpty(r)); + Assert.assertNotNull(r.getBytes());//should be empty byte[], but could change this behavior + Assert.assertEquals(3, r.getBytes().length); + Assert.assertEquals(0, r.getOffset()); + Assert.assertEquals(3, r.getLength()); + + //cloning (deep copying) + Assert.assertTrue(Bytes.equals(new byte[]{1, 3, 2}, r.deepCopyToNewArray())); + Assert.assertNotSame(r.getBytes(), r.deepCopyToNewArray()); + + //hash code + Assert.assertTrue(r.hashCode() > 0); + Assert.assertEquals(r.hashCode(), r.deepCopy().hashCode()); + + //copying to arrays + byte[] destination = new byte[]{-59};//junk + r.deepCopySubRangeTo(2, 1, destination, 0); + Assert.assertTrue(Bytes.equals(new byte[]{2}, destination)); + + //set length + r.setLength(1); + Assert.assertTrue(Bytes.equals(new byte[]{1}, r.deepCopyToNewArray())); + r.setLength(2);//verify we retained the 2nd byte, but dangerous in real code + Assert.assertTrue(Bytes.equals(new byte[]{1, 3}, r.deepCopyToNewArray())); + } +} diff --git a/hbase-prefix-tree/src/main/java/org/apache/hadoop/hbase/codec/prefixtree/decode/row/RowNodeReader.java b/hbase-prefix-tree/src/main/java/org/apache/hadoop/hbase/codec/prefixtree/decode/row/RowNodeReader.java index ffe1e1a..efd6c05 100644 --- a/hbase-prefix-tree/src/main/java/org/apache/hadoop/hbase/codec/prefixtree/decode/row/RowNodeReader.java +++ b/hbase-prefix-tree/src/main/java/org/apache/hadoop/hbase/codec/prefixtree/decode/row/RowNodeReader.java @@ -20,7 +20,7 @@ package org.apache.hadoop.hbase.codec.prefixtree.decode.row; import org.apache.hadoop.classification.InterfaceAudience; import org.apache.hadoop.hbase.codec.prefixtree.PrefixTreeBlockMeta; -import org.apache.hadoop.hbase.util.ByteRange; +import org.apache.hadoop.hbase.util.SimpleByteRange; import org.apache.hadoop.hbase.util.Bytes; import org.apache.hadoop.hbase.util.vint.UFIntTool; import org.apache.hadoop.hbase.util.vint.UVIntTool; @@ -202,7 +202,7 @@ public class RowNodeReader { public byte[] getToken() { // TODO pass in reusable ByteRange - return new ByteRange(block, tokenOffset, tokenLength).deepCopyToNewArray(); + return new SimpleByteRange(block, tokenOffset, tokenLength).deepCopyToNewArray(); } public int getOffset() { diff --git a/hbase-prefix-tree/src/main/java/org/apache/hadoop/hbase/codec/prefixtree/encode/PrefixTreeEncoder.java b/hbase-prefix-tree/src/main/java/org/apache/hadoop/hbase/codec/prefixtree/encode/PrefixTreeEncoder.java index 7817c38..6971d8b 100644 --- a/hbase-prefix-tree/src/main/java/org/apache/hadoop/hbase/codec/prefixtree/encode/PrefixTreeEncoder.java +++ b/hbase-prefix-tree/src/main/java/org/apache/hadoop/hbase/codec/prefixtree/encode/PrefixTreeEncoder.java @@ -36,6 +36,7 @@ import org.apache.hadoop.hbase.codec.prefixtree.encode.tokenize.Tokenizer; import org.apache.hadoop.hbase.io.CellOutputStream; import org.apache.hadoop.hbase.util.ArrayUtils; import org.apache.hadoop.hbase.util.ByteRange; +import org.apache.hadoop.hbase.util.SimpleByteRange; import org.apache.hadoop.hbase.util.byterange.ByteRangeSet; import org.apache.hadoop.hbase.util.byterange.impl.ByteRangeHashSet; import org.apache.hadoop.hbase.util.byterange.impl.ByteRangeTreeSet; @@ -146,9 +147,9 @@ public class PrefixTreeEncoder implements CellOutputStream { public PrefixTreeEncoder(OutputStream outputStream, boolean includeMvccVersion) { // used during cell accumulation this.blockMeta = new PrefixTreeBlockMeta(); - this.rowRange = new ByteRange(); - this.familyRange = new ByteRange(); - this.qualifierRange = new ByteRange(); + this.rowRange = new SimpleByteRange(); + this.familyRange = new SimpleByteRange(); + this.qualifierRange = new SimpleByteRange(); this.timestamps = new long[INITIAL_PER_CELL_ARRAY_SIZES]; this.mvccVersions = new long[INITIAL_PER_CELL_ARRAY_SIZES]; this.typeBytes = new byte[INITIAL_PER_CELL_ARRAY_SIZES]; @@ -488,7 +489,7 @@ public class PrefixTreeEncoder implements CellOutputStream { } public ByteRange getValueByteRange() { - return new ByteRange(values, 0, totalValueBytes); + return new SimpleByteRange(values, 0, totalValueBytes); } } diff --git a/hbase-prefix-tree/src/main/java/org/apache/hadoop/hbase/codec/prefixtree/encode/row/RowNodeWriter.java b/hbase-prefix-tree/src/main/java/org/apache/hadoop/hbase/codec/prefixtree/encode/row/RowNodeWriter.java index 29ebafa..d253e39 100644 --- a/hbase-prefix-tree/src/main/java/org/apache/hadoop/hbase/codec/prefixtree/encode/row/RowNodeWriter.java +++ b/hbase-prefix-tree/src/main/java/org/apache/hadoop/hbase/codec/prefixtree/encode/row/RowNodeWriter.java @@ -28,7 +28,7 @@ import org.apache.hadoop.classification.InterfaceAudience; import org.apache.hadoop.hbase.codec.prefixtree.PrefixTreeBlockMeta; import org.apache.hadoop.hbase.codec.prefixtree.encode.PrefixTreeEncoder; import org.apache.hadoop.hbase.codec.prefixtree.encode.tokenize.TokenizerNode; -import org.apache.hadoop.hbase.util.ByteRangeTool; +import org.apache.hadoop.hbase.util.ByteRangeUtils; import org.apache.hadoop.hbase.util.CollectionUtils; import org.apache.hadoop.hbase.util.vint.UFIntTool; import org.apache.hadoop.hbase.util.vint.UVIntTool; @@ -155,7 +155,7 @@ public class RowNodeWriter{ protected void writeRowToken(OutputStream os) throws IOException { UVIntTool.writeBytes(tokenWidth, os); int tokenStartIndex = tokenizerNode.isRoot() ? 0 : 1; - ByteRangeTool.write(os, tokenizerNode.getToken(), tokenStartIndex); + ByteRangeUtils.write(os, tokenizerNode.getToken(), tokenStartIndex); } /** diff --git a/hbase-prefix-tree/src/main/java/org/apache/hadoop/hbase/codec/prefixtree/encode/tokenize/TokenizerNode.java b/hbase-prefix-tree/src/main/java/org/apache/hadoop/hbase/codec/prefixtree/encode/tokenize/TokenizerNode.java index 077b5f5..fc78cd8 100644 --- a/hbase-prefix-tree/src/main/java/org/apache/hadoop/hbase/codec/prefixtree/encode/tokenize/TokenizerNode.java +++ b/hbase-prefix-tree/src/main/java/org/apache/hadoop/hbase/codec/prefixtree/encode/tokenize/TokenizerNode.java @@ -23,8 +23,10 @@ import java.util.List; import org.apache.hadoop.classification.InterfaceAudience; import org.apache.hadoop.hbase.util.ByteRange; +import org.apache.hadoop.hbase.util.ByteRangeUtils; import org.apache.hadoop.hbase.util.Bytes; import org.apache.hadoop.hbase.util.CollectionUtils; +import org.apache.hadoop.hbase.util.SimpleByteRange; import org.apache.hadoop.hbase.util.Strings; import com.google.common.collect.Lists; @@ -135,7 +137,7 @@ public class TokenizerNode{ public TokenizerNode(Tokenizer builder, TokenizerNode parent, int nodeDepth, int tokenStartOffset, int tokenOffset, int tokenLength) { - this.token = new ByteRange(); + this.token = new SimpleByteRange(); reconstruct(builder, parent, nodeDepth, tokenStartOffset, tokenOffset, tokenLength); this.children = Lists.newArrayList(); } @@ -164,7 +166,7 @@ public class TokenizerNode{ parent = null; nodeDepth = 0; tokenStartOffset = 0; - token.clear(); + token.unset(); numOccurrences = 0; children.clear();// branches & nubs @@ -298,7 +300,7 @@ public class TokenizerNode{ } protected int numIdenticalBytes(ByteRange bytes) { - return token.numEqualPrefixBytes(bytes, tokenStartOffset); + return ByteRangeUtils.numEqualPrefixBytes(token, bytes, tokenStartOffset); } diff --git a/hbase-prefix-tree/src/main/java/org/apache/hadoop/hbase/util/byterange/ByteRangeSet.java b/hbase-prefix-tree/src/main/java/org/apache/hadoop/hbase/util/byterange/ByteRangeSet.java index 570d489..db6fc51 100644 --- a/hbase-prefix-tree/src/main/java/org/apache/hadoop/hbase/util/byterange/ByteRangeSet.java +++ b/hbase-prefix-tree/src/main/java/org/apache/hadoop/hbase/util/byterange/ByteRangeSet.java @@ -26,6 +26,7 @@ import org.apache.hadoop.classification.InterfaceAudience; import org.apache.hadoop.hbase.util.ArrayUtils; import org.apache.hadoop.hbase.util.ByteRange; import org.apache.hadoop.hbase.util.Bytes; +import org.apache.hadoop.hbase.util.SimpleByteRange; import com.google.common.collect.Lists; @@ -105,7 +106,7 @@ public abstract class ByteRangeSet { protected int store(ByteRange bytes) { int indexOfNewElement = numUniqueRanges; if (uniqueRanges.size() <= numUniqueRanges) { - uniqueRanges.add(new ByteRange()); + uniqueRanges.add(new SimpleByteRange()); } ByteRange storedRange = uniqueRanges.get(numUniqueRanges); int neededBytes = numBytes + bytes.getLength(); diff --git a/hbase-prefix-tree/src/main/java/org/apache/hadoop/hbase/util/byterange/impl/ByteRangeTreeSet.java b/hbase-prefix-tree/src/main/java/org/apache/hadoop/hbase/util/byterange/impl/ByteRangeTreeSet.java index eb86bc9..8dd9998 100644 --- a/hbase-prefix-tree/src/main/java/org/apache/hadoop/hbase/util/byterange/impl/ByteRangeTreeSet.java +++ b/hbase-prefix-tree/src/main/java/org/apache/hadoop/hbase/util/byterange/impl/ByteRangeTreeSet.java @@ -36,7 +36,7 @@ public class ByteRangeTreeSet extends ByteRangeSet { /************************ constructors *****************************/ public ByteRangeTreeSet() { - this.uniqueIndexByUniqueRange = new TreeMap