/*
* 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;
/**
* Lightweight, reusable class for specifying ranges of byte[]'s. CompareTo and equals methods are
* lexicographic, which is native to HBase.
* <p/>
* This class differs from ByteBuffer:
* <li/>On-heap bytes only
* <li/>Implements equals, hashCode, and compareTo so that it can be used in standard java
* Collections, similar to String.
* <li/>Does not maintain mark/position iterator state inside the class. Doing so leads to many bugs
* in complex applications.
* <li/>Allows the addition of simple core methods like this.copyTo(that, offset).
* <li/>Can be reused in tight loops like a major compaction which can save significant amounts of
* garbage.
* <li/>(Without reuse, we throw off garbage like this thing:
* http://www.youtube.com/watch?v=lkmBH-MjZF4
* <p/>
* Mutable, and always evaluates equals, hashCode, and compareTo based on the current contents.
* <p/>
* Can contain convenience methods for comparing, printing, cloning, spawning new arrays, copying to
* other arrays, etc. Please place non-core methods into {@link ByteRangeTool}.
* <p/>
* We may consider converting this to an interface and creating separate implementations for a
* single byte[], a paged byte[] (growable byte[][]), a ByteBuffer, etc
*/
public class ByteRange implements Comparable<ByteRange> {
private static final int UNSET_HASH_VALUE = -1;
/********************** fields *****************************/
// 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.
*/
private byte[] bytes;
/**
* The index of the first byte in this range. ByteRange.get(0) will return bytes[offset].
*/
private int offset;
/**
* The number of bytes in the range. Offset + length must be <= bytes.length
*/
private 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;
/********************** construct ***********************/
public ByteRange() {
set(new byte[0]);//Could probably get away with a null array if the need arises.
}
public ByteRange(byte[] bytes) {
set(bytes);
}
public ByteRange(byte[] bytes, int offset, int length) {
set(bytes, offset, length);
}
/********************** write methods *************************/
public ByteRange clear() {
clearHashCache();
bytes = null;
offset = 0;
length = 0;
return this;
}
public ByteRange set(byte[] bytes) {
clearHashCache();
this.bytes = bytes;
this.offset = 0;
this.length = ArrayUtils.length(bytes);
return this;
}
public ByteRange set(byte[] bytes, int offset, int length) {
clearHashCache();
this.bytes = bytes;
this.offset = offset;
this.length = length;
return this;
}
public void setLength(int length) {
clearHashCache();
this.length = length;
}
/*********** read methods (add non-core methods to ByteRangeUtils) *************/
/**
* @param index zero-based index
* @return single byte at index
*/
public byte get(int index) {
return bytes[offset + index];
}
/**
* Instantiate a new byte[] with exact length, which is at least 24 bytes + length. Copy the
* contents of this range into it.
* @return The newly cloned byte[].
*/
public byte[] deepCopyToNewArray() {
byte[] result = new byte[length];
System.arraycopy(bytes, offset, result, 0, length);
return result;
}
/**
* Create a new ByteRange with new backing byte[] and copy the state of this range into the new
* range. Copy the hash over if it is already calculated.
* @return Deep copy
*/
public ByteRange deepCopy() {
ByteRange clone = new ByteRange(deepCopyToNewArray());
if (isHashCached()) {
clone.hash = hash;
}
return clone;
}
/**
* Wrapper for System.arraycopy. Copy the contents of this range into the provided array.
* @param destination Copy to this array
* @param destinationOffset First index in the destination array.
*/
public void deepCopyTo(byte[] destination, int destinationOffset) {
System.arraycopy(bytes, offset, destination, destinationOffset, length);
}
/**
* Wrapper for System.arraycopy. Copy the contents of this range into the provided array.
* @param innerOffset Start copying from this index in this source ByteRange. First byte copied is
* bytes[offset + innerOffset]
* @param copyLength Copy this many bytes
* @param destination Copy to this array
* @param destinationOffset First index in the destination array.
*/
public void deepCopySubRangeTo(int innerOffset, int copyLength, byte[] destination,
int destinationOffset) {
System.arraycopy(bytes, offset + innerOffset, destination, destinationOffset, copyLength);
}
/**
* Create a new ByteRange that points at this range's byte[]. The new range can have different
* values for offset and length, but modifying the shallowCopy will modify the bytes in this
* range's array. Pass over the hash code if it is already cached.
* @param innerOffset First byte of clone will be this.offset + copyOffset.
* @param copyLength Number of bytes in the clone.
* @return new ByteRange object referencing this range's byte[].
*/
public ByteRange shallowCopySubRange(int innerOffset, int copyLength) {
ByteRange clone = new ByteRange(bytes, offset + innerOffset, copyLength);
if (isHashCached()) {
clone.hash = hash;
}
return clone;
}
//TODO move to ByteRangeUtils because it is non-core method
public int numEqualPrefixBytes(ByteRange that, int thatInnerOffset) {
int maxCompares = Math.min(length, that.length - thatInnerOffset);
for (int i = 0; i < maxCompares; ++i) {
if (bytes[offset + i] != that.bytes[that.offset + thatInnerOffset + i]) {
return i;
}
}
return maxCompares;
}
public byte[] getBytes() {
return bytes;
}
public int getOffset() {
return offset;
}
public int getLength() {
return length;
}
public boolean isEmpty(){
return isEmpty(this);
}
public boolean notEmpty(){
return notEmpty(this);
}
/******************* static methods ************************/
public static boolean isEmpty(ByteRange range){
return range == null || range.length == 0;
}
public static boolean notEmpty(ByteRange range){
return range != null && range.length > 0;
}
/******************* standard methods *********************/
@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 ByteRange)) {
return false;
}
ByteRange that = (ByteRange) 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;
}
private 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.bytes, other.offset, other.length);
}
@Override
public String toString() {
return Bytes.toStringBinary(bytes, offset, length);
}
}