blob: cc582f71f98c734b172932fbe6712344b221c847 [file] [log] [blame]
/*
* 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.phoenix.util;
import java.io.ByteArrayInputStream;
import java.io.DataInput;
import java.io.DataInputStream;
import java.io.DataOutput;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import java.util.Set;
import org.apache.hadoop.hbase.HConstants;
import org.apache.hadoop.hbase.filter.CompareFilter.CompareOp;
import org.apache.hadoop.hbase.io.ImmutableBytesWritable;
import org.apache.hadoop.hbase.util.Bytes;
import org.apache.hadoop.io.WritableUtils;
import org.apache.phoenix.hbase.index.util.ImmutableBytesPtr;
import org.apache.phoenix.query.KeyRange;
import org.apache.phoenix.schema.SortOrder;
import org.apache.phoenix.schema.types.PDataType;
import com.google.common.base.Preconditions;
/**
*
* Byte utilities
*
*
* @since 0.1
*/
public class ByteUtil {
public static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
public static final ImmutableBytesPtr EMPTY_BYTE_ARRAY_PTR = new ImmutableBytesPtr(
EMPTY_BYTE_ARRAY);
public static final ImmutableBytesWritable EMPTY_IMMUTABLE_BYTE_ARRAY = new ImmutableBytesWritable(
EMPTY_BYTE_ARRAY);
public static final Comparator<ImmutableBytesPtr> BYTES_PTR_COMPARATOR = new Comparator<ImmutableBytesPtr>() {
@Override
public int compare(ImmutableBytesPtr o1, ImmutableBytesPtr o2) {
return Bytes.compareTo(o1.get(), o1.getOffset(), o1.getLength(), o2.get(), o2.getOffset(), o2.getLength());
}
};
/**
* Serialize an array of byte arrays into a single byte array. Used
* to pass through a set of bytes arrays as an attribute of a Scan.
* Use {@link #toByteArrays(byte[], int)} to convert the serialized
* byte array back to the array of byte arrays.
* @param byteArrays the array of byte arrays to serialize
* @return the byte array
*/
public static byte[] toBytes(byte[][] byteArrays) {
int size = 0;
for (byte[] b : byteArrays) {
if (b == null) {
size++;
} else {
size += b.length;
size += WritableUtils.getVIntSize(b.length);
}
}
TrustedByteArrayOutputStream bytesOut = new TrustedByteArrayOutputStream(size);
DataOutputStream out = new DataOutputStream(bytesOut);
try {
for (byte[] b : byteArrays) {
if (b == null) {
WritableUtils.writeVInt(out, 0);
} else {
WritableUtils.writeVInt(out, b.length);
out.write(b);
}
}
} catch (IOException e) {
throw new RuntimeException(e); // not possible
} finally {
try {
out.close();
} catch (IOException e) {
throw new RuntimeException(e); // not possible
}
}
return bytesOut.getBuffer();
}
/**
* Deserialize a byte array into a set of byte arrays. Used in
* coprocessor to reconstruct byte arrays from attribute value
* passed through the Scan.
* @param b byte array containing serialized byte arrays (created by {@link #toBytes(byte[][])}).
* @param length number of byte arrays that were serialized
* @return array of now deserialized byte arrays
* @throws IllegalStateException if there are more than length number of byte arrays that were serialized
*/
public static byte[][] toByteArrays(byte[] b, int length) {
return toByteArrays(b, 0, length);
}
public static byte[][] toByteArrays(byte[] b, int offset, int length) {
ByteArrayInputStream bytesIn = new ByteArrayInputStream(b, offset, b.length - offset);
DataInputStream in = new DataInputStream(bytesIn);
byte[][] byteArrays = new byte[length][];
try {
for (int i = 0; i < length; i++) {
int bLength = WritableUtils.readVInt(in);
if (bLength == 0) {
byteArrays[i] = null;
} else {
byteArrays[i] = new byte[bLength];
int rLength = in.read(byteArrays[i], 0, bLength);
assert (rLength == bLength); // For find bugs
}
}
if (in.read() != -1) {
throw new IllegalStateException("Expected only " + length + " byte arrays, but found more");
}
return byteArrays;
} catch (IOException e) {
throw new RuntimeException(e); // not possible
} finally {
try {
in.close();
} catch (IOException e) {
throw new RuntimeException(e); // not possible
}
}
}
public static byte[] serializeVIntArray(int[] intArray) {
return serializeVIntArray(intArray,intArray.length);
}
public static byte[] serializeVIntArray(int[] intArray, int encodedLength) {
int size = WritableUtils.getVIntSize(encodedLength);
for (int i = 0; i < intArray.length; i++) {
size += WritableUtils.getVIntSize(intArray[i]);
}
int offset = 0;
byte[] out = new byte[size];
offset += ByteUtil.vintToBytes(out, offset, size);
for (int i = 0; i < intArray.length; i++) {
offset += ByteUtil.vintToBytes(out, offset, intArray[i]);
}
return out;
}
public static void serializeVIntArray(DataOutput output, int[] intArray) throws IOException {
serializeVIntArray(output, intArray, intArray.length);
}
/**
* Allows additional stuff to be encoded in length
* @param output
* @param intArray
* @param encodedLength
* @throws IOException
*/
public static void serializeVIntArray(DataOutput output, int[] intArray, int encodedLength) throws IOException {
WritableUtils.writeVInt(output, encodedLength);
for (int i = 0; i < intArray.length; i++) {
WritableUtils.writeVInt(output, intArray[i]);
}
}
public static long[] readFixedLengthLongArray(DataInput input, int length) throws IOException {
long[] longArray = new long[length];
for (int i = 0; i < length; i++) {
longArray[i] = input.readLong();
}
return longArray;
}
public static void writeFixedLengthLongArray(DataOutput output, long[] longArray) throws IOException {
for (int i = 0; i < longArray.length; i++) {
output.writeLong(longArray[i]);
}
}
/**
* Deserialize a byte array into a int array.
* @param b byte array storing serialized vints
* @return int array
*/
public static int[] deserializeVIntArray(byte[] b) {
ByteArrayInputStream bytesIn = new ByteArrayInputStream(b);
DataInputStream in = new DataInputStream(bytesIn);
try {
int length = WritableUtils.readVInt(in);
return deserializeVIntArray(in, length);
} catch (IOException e) {
throw new RuntimeException(e); // not possible
} finally {
try {
in.close();
} catch (IOException e) {
throw new RuntimeException(e); // not possible
}
}
}
public static int[] deserializeVIntArray(DataInput in) throws IOException {
return deserializeVIntArray(in, WritableUtils.readVInt(in));
}
public static int[] deserializeVIntArray(DataInput in, int length) throws IOException {
int i = 0;
int[] intArray = new int[length];
while (i < length) {
intArray[i++] = WritableUtils.readVInt(in);
}
return intArray;
}
/**
* Deserialize a byte array into a int array.
* @param b byte array storing serialized vints
* @param length number of serialized vints
* @return int array
*/
public static int[] deserializeVIntArray(byte[] b, int length) {
ByteArrayInputStream bytesIn = new ByteArrayInputStream(b);
DataInputStream in = new DataInputStream(bytesIn);
try {
return deserializeVIntArray(in,length);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* Concatenate together one or more byte arrays
* @param first first byte array
* @param rest rest of byte arrays
* @return newly allocated byte array that is a concatenation of all the byte arrays passed in
*/
public static byte[] concat(byte[] first, byte[]... rest) {
int totalLength = first.length;
for (byte[] array : rest) {
totalLength += array.length;
}
byte[] result = Arrays.copyOf(first, totalLength);
int offset = first.length;
for (byte[] array : rest) {
System.arraycopy(array, 0, result, offset, array.length);
offset += array.length;
}
return result;
}
public static <T> T[] concat(T[] first, T[]... rest) {
int totalLength = first.length;
for (T[] array : rest) {
totalLength += array.length;
}
T[] result = Arrays.copyOf(first, totalLength);
int offset = first.length;
for (T[] array : rest) {
System.arraycopy(array, 0, result, offset, array.length);
offset += array.length;
}
return result;
}
public static byte[] concat(SortOrder sortOrder, ImmutableBytesWritable... writables) {
Preconditions.checkNotNull(sortOrder);
int totalLength = 0;
for (ImmutableBytesWritable writable : writables) {
totalLength += writable.getLength();
}
byte[] result = new byte[totalLength];
int totalOffset = 0;
for (ImmutableBytesWritable array : writables) {
byte[] bytes = array.get();
int offset = array.getOffset();
if (sortOrder == SortOrder.DESC) {
bytes = SortOrder.invert(bytes, offset, new byte[array.getLength()], 0, array.getLength());
offset = 0;
}
System.arraycopy(bytes, offset, result, totalOffset, array.getLength());
totalOffset += array.getLength();
}
return result;
}
public static int vintFromBytes(byte[] buffer, int offset) {
try {
return (int)Bytes.readVLong(buffer, offset);
} catch (IOException e) { // Impossible
throw new RuntimeException(e);
}
}
/**
* Decode a vint from the buffer pointed at to by ptr and
* increment the offset of the ptr by the length of the
* vint.
* @param ptr a pointer to a byte array buffer
* @return the decoded vint value as an int
*/
public static int vintFromBytes(ImmutableBytesWritable ptr) {
return (int) vlongFromBytes(ptr);
}
/**
* Decode a vint from the buffer pointed at to by ptr and
* increment the offset of the ptr by the length of the
* vint.
* @param ptr a pointer to a byte array buffer
* @return the decoded vint value as a long
*/
public static long vlongFromBytes(ImmutableBytesWritable ptr) {
final byte [] buffer = ptr.get();
final int offset = ptr.getOffset();
byte firstByte = buffer[offset];
int len = WritableUtils.decodeVIntSize(firstByte);
if (len == 1) {
ptr.set(buffer, offset+1, ptr.getLength());
return firstByte;
}
long i = 0;
for (int idx = 0; idx < len-1; idx++) {
byte b = buffer[offset + 1 + idx];
i = i << 8;
i = i | (b & 0xFF);
}
ptr.set(buffer, offset+len, ptr.getLength());
return (WritableUtils.isNegativeVInt(firstByte) ? ~i : i);
}
/**
* Put long as variable length encoded number at the offset in the result byte array
* @param vint Integer to make a vint of.
* @param result buffer to put vint into
* @return Vint length in bytes of vint
*/
public static int vintToBytes(byte[] result, int offset, final long vint) {
long i = vint;
if (i >= -112 && i <= 127) {
result[offset] = (byte) i;
return 1;
}
int len = -112;
if (i < 0) {
i ^= -1L; // take one's complement'
len = -120;
}
long tmp = i;
while (tmp != 0) {
tmp = tmp >> 8;
len--;
}
result[offset++] = (byte) len;
len = (len < -120) ? -(len + 120) : -(len + 112);
for (int idx = len; idx != 0; idx--) {
int shiftbits = (idx - 1) * 8;
long mask = 0xFFL << shiftbits;
result[offset++] = (byte)((i & mask) >> shiftbits);
}
return len + 1;
}
/**
* Increment the key to the next key
* @param key the key to increment
* @return a new byte array with the next key or null
* if the key could not be incremented because it's
* already at its max value.
*/
public static byte[] nextKey(byte[] key) {
byte[] nextStartRow = new byte[key.length];
System.arraycopy(key, 0, nextStartRow, 0, key.length);
if (!nextKey(nextStartRow, nextStartRow.length)) {
return null;
}
return nextStartRow;
}
/**
* Increment the key in-place to the next key
* @param key the key to increment
* @param length the length of the key
* @return true if the key can be incremented and
* false otherwise if the key is at its max
* value.
*/
public static boolean nextKey(byte[] key, int length) {
return nextKey(key, 0, length);
}
public static boolean nextKey(byte[] key, int offset, int length) {
if (length == 0) {
return false;
}
int i = offset + length - 1;
while (key[i] == -1) {
key[i] = 0;
i--;
if (i < offset) {
// Change bytes back to the way they were
do {
key[++i] = -1;
} while (i < offset + length - 1);
return false;
}
}
key[i] = (byte)(key[i] + 1);
return true;
}
public static byte[] previousKey(byte[] key) {
byte[] previousKey = new byte[key.length];
System.arraycopy(key, 0, previousKey, 0, key.length);
if (!previousKey(previousKey, previousKey.length)) {
return null;
}
return previousKey;
}
public static boolean previousKey(byte[] key, int length) {
return previousKey(key, 0, length);
}
public static boolean previousKey(byte[] key, int offset, int length) {
if (length == 0) {
return false;
}
int i = offset + length - 1;
while (key[i] == 0) {
key[i] = -1;
i--;
if (i < offset) {
// Change bytes back to the way they were
do {
key[++i] = 0;
} while (i < offset + length - 1);
return false;
}
}
key[i] = (byte)(key[i] - 1);
return true;
}
/**
* Expand the key to length bytes using a null byte.
*/
public static byte[] fillKey(byte[] key, int length) {
if(key.length > length) {
throw new IllegalStateException();
}
if (key.length == length) {
return key;
}
byte[] newBound = new byte[length];
System.arraycopy(key, 0, newBound, 0, key.length);
return newBound;
}
/**
* Expand the key to length bytes using the fillByte to fill the
* bytes beyond the current key length.
*/
public static void nullPad(ImmutableBytesWritable ptr, int length) {
if(ptr.getLength() > length) {
throw new IllegalStateException();
}
if (ptr.getLength() == length) {
return;
}
byte[] newBound = new byte[length];
System.arraycopy(ptr.get(), ptr.getOffset(), newBound, 0, ptr.getLength());
ptr.set(newBound);
}
/**
* Get the size in bytes of the UTF-8 encoded CharSequence
* @param sequence the CharSequence
*/
public static int getSize(CharSequence sequence) {
int count = 0;
for (int i = 0, len = sequence.length(); i < len; i++) {
char ch = sequence.charAt(i);
if (ch <= 0x7F) {
count++;
} else if (ch <= 0x7FF) {
count += 2;
} else if (Character.isHighSurrogate(ch)) {
count += 4;
++i;
} else {
count += 3;
}
}
return count;
}
public static boolean isInclusive(CompareOp op) {
switch (op) {
case LESS:
case GREATER:
return false;
case EQUAL:
case NOT_EQUAL:
case LESS_OR_EQUAL:
case GREATER_OR_EQUAL:
return true;
default:
throw new RuntimeException("Unknown Compare op " + op.name());
}
}
public static boolean compare(CompareOp op, int compareResult) {
switch (op) {
case LESS:
return compareResult < 0;
case LESS_OR_EQUAL:
return compareResult <= 0;
case EQUAL:
return compareResult == 0;
case NOT_EQUAL:
return compareResult != 0;
case GREATER_OR_EQUAL:
return compareResult >= 0;
case GREATER:
return compareResult > 0;
default:
throw new RuntimeException("Unknown Compare op " + op.name());
}
}
/**
* Given an ImmutableBytesWritable, returns the payload part of the argument as an byte array.
*/
public static byte[] copyKeyBytesIfNecessary(ImmutableBytesWritable ptr) {
if (ptr.getOffset() == 0 && ptr.getLength() == ptr.get().length) {
return ptr.get();
}
return ptr.copyBytes();
}
public static KeyRange getKeyRange(byte[] key, CompareOp op, PDataType type) {
switch (op) {
case EQUAL:
return type.getKeyRange(key, true, key, true);
case GREATER:
return type.getKeyRange(key, false, KeyRange.UNBOUND, false);
case GREATER_OR_EQUAL:
return type.getKeyRange(key, true, KeyRange.UNBOUND, false);
case LESS:
return type.getKeyRange(KeyRange.UNBOUND, false, key, false);
case LESS_OR_EQUAL:
return type.getKeyRange(KeyRange.UNBOUND, false, key, true);
default:
throw new IllegalArgumentException("Unknown operator " + op);
}
}
public static boolean contains(Collection<byte[]> keys, byte[] key) {
for (byte[] k : keys) {
if (Arrays.equals(k, key)) { return true; }
}
return false;
}
public static boolean contains(List<ImmutableBytesPtr> keys, ImmutableBytesPtr key) {
for (ImmutableBytesPtr k : keys) {
if (key.compareTo(k) == 0) { return true; }
}
return false;
}
public static boolean match(Set<byte[]> keys, Set<byte[]> keys2) {
if (keys == keys2) return true;
if (keys == null || keys2 == null) return false;
int size = keys.size();
if (keys2.size() != size) return false;
for (byte[] k : keys) {
if (!contains(keys2, k)) { return false; }
}
return true;
}
public static byte[] calculateTheClosestNextRowKeyForPrefix(byte[] rowKeyPrefix) {
// Essentially we are treating it like an 'unsigned very very long' and doing +1 manually.
// Search for the place where the trailing 0xFFs start
int offset = rowKeyPrefix.length;
while (offset > 0) {
if (rowKeyPrefix[offset - 1] != (byte) 0xFF) {
break;
}
offset--;
}
if (offset == 0) {
// We got an 0xFFFF... (only FFs) stopRow value which is
// the last possible prefix before the end of the table.
// So set it to stop at the 'end of the table'
return HConstants.EMPTY_END_ROW;
}
// Copy the right length of the original
byte[] newStopRow = Arrays.copyOfRange(rowKeyPrefix, 0, offset);
// And increment the last one
newStopRow[newStopRow.length - 1]++;
return newStopRow;
}
public static byte[][] splitArrayBySeparator(byte[] src, byte separator){
List<Integer> separatorLocations = new ArrayList<Integer>();
for (int k = 0; k < src.length; k++){
if (src[k] == separator){
separatorLocations.add(k);
}
}
byte[][] dst = new byte[separatorLocations.size() +1][];
int previousSepartor = -1;
for (int j = 0; j < separatorLocations.size(); j++){
int separatorLocation = separatorLocations.get(j);
dst[j] = Bytes.copy(src, previousSepartor +1, separatorLocation- previousSepartor -1);
previousSepartor = separatorLocation;
}
if (previousSepartor < src.length){
dst[separatorLocations.size()] = Bytes.copy(src,
previousSepartor +1, src.length - previousSepartor -1);
}
return dst;
}
}