package com.cloudhopper.mq.util;
/*
* #%L
* ch-mq
* %%
* Copyright (C) 2012 Cloudhopper by Twitter
* %%
* Licensed 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.
* #L%
*/
import com.cloudhopper.mq.util.CompositeKey;
/**
* A utility class for working with CompositeKey instances. Methods for
* encoding, decoding, and comparing their values.
*
* @author joelauer
*/
public class CompositeKeyUtil {
//
// ranges of values for unsigned
//
// 1 byte: 0-255
// 2 bytes: 0-65535
// 3 bytes: 0-16,777,215
// 4 bytes: 0-4,294,967,295
// 5 bytes: 0-1,099,511,627,775
// 6 bytes: 0-281,474,976,710,655
// 7 bytes: 0-72,057,594,037,927,935
// 8 bytes: 0-18,446,744,073,709,551,615
// 8 bytes (signed): 9,223,372,036,854,775,808
private final int queueIdByteLength;
private final int itemIdByteLength;
/**
* Creates a utility class for working with CompositeKeys.
* @param queueIdByteLength The number of bytes to use when encoding/decoding
* the CompositeKey. Valid number of bytes is 1 to 3. If 1 byte, the
* queueId can range from 0-255, if 2 bytes can range from 0-65535, and
* if 3 bytes can range from 0-16,777,215.
* @param itemIdByteLength The number of bytes to use when encoding/decoding
* the CompositeKey. Valid number of bytes is 1 to 7. If 1 byte, max
* value of 255 items. If 2 bytes, max value of 65535 items. If 3 bytes,
* max value of 16,777,215 items. If 4 bytes, max value of 4,294,967,295 items.
* If 5 bytes, max value of 1,099,511,627,775 items. If 6 bytes, max value
* of 281,474,976,710,655 items. If 7 bytes, max value of 72,057,594,037,927,935 items.
*/
public CompositeKeyUtil(int queueIdByteLength, int itemIdByteLength) {
this.queueIdByteLength = queueIdByteLength;
this.itemIdByteLength = itemIdByteLength;
// these methods validate the lengths
getMaxQueueId();
getMaxItemId();
}
public int getQueueIdByteLength() {
return this.queueIdByteLength;
}
public int getItemIdByteLength() {
return this.itemIdByteLength;
}
public int getByteLength() {
return (this.queueIdByteLength + this.itemIdByteLength);
}
/**
* Based on configured number of bytes for queueId length, returns the maximum
* possible value for a queueId.
* @return The maximum possible value for a queueId based on configured number
* of bytes.
*/
public int getMaxQueueId() {
return getMaxQueueId(queueIdByteLength);
}
static public int getMaxQueueId(int byteLength) {
if (byteLength == 1) {
return 255;
} else if (byteLength == 2) {
return 65535;
} else if (byteLength == 3) {
return 16777215;
} else {
throw new IllegalArgumentException("Invalid byteLength for queueId (min=1, max=3)");
}
}
static public int getQueueIdByteLength(int maxQueues) {
for (int i = 1; i <= 3; i++) {
int maxQueueSize = getMaxQueueId(i) + 1;
if (maxQueueSize >= maxQueues) {
return i;
}
}
return -1;
}
/**
* Based on configured number of bytes for itemId length, returns the maximum
* possible value for a itemId. If 1 byte, this would return 255. For the
* total maximum number of elements, add 1 to find the count.
* @return The maximum possible value for a itemId based on configured number
* of bytes.
*/
public long getMaxItemId() {
if (itemIdByteLength == 1) {
return 255;
} else if (itemIdByteLength == 2) {
return 65535;
} else if (itemIdByteLength == 3) {
return 16777215;
} else if (itemIdByteLength == 4) {
return 4294967295L;
} else if (itemIdByteLength == 5) {
return 1099511627775L;
} else if (itemIdByteLength == 6) {
return 281474976710655L;
} else if (itemIdByteLength == 7) {
return 72057594037927935L;
} else {
throw new IllegalArgumentException("Invalid number of bytes configured for itemId (min=1, max=7)");
}
}
static public long getMaxItemId(int byteLength) {
if (byteLength == 1) {
return 255;
} else if (byteLength == 2) {
return 65535;
} else if (byteLength == 3) {
return 16777215;
} else if (byteLength == 4) {
return 4294967295L;
} else if (byteLength == 5) {
return 1099511627775L;
} else if (byteLength == 6) {
return 281474976710655L;
} else if (byteLength == 7) {
return 72057594037927935L;
} else {
throw new IllegalArgumentException("Invalid number of bytes configured for itemId (min=1, max=7)");
}
}
static public int getItemIdByteLength(long itemsPerQueue) {
for (int i = 1; i <= 7; i++) {
long maxItemsPerQueue = getMaxItemId(i) + 1;
if (maxItemsPerQueue >= itemsPerQueue) {
return i;
}
}
return -1;
}
/**
* Gets the maximum number of items this composite key can store. Internally,
* this returns getMaxItemId() + 1.
* @return The maximum number of items that can be represented by this key
*/
public long getMaxItems() {
return getMaxItemId() + 1;
}
public byte[] encode(CompositeKey key) {
return encode(key.getQueueId(), key.getItemId());
}
public byte[] encode(int queueId, long itemId) {
if (queueId < 0 || queueId > getMaxQueueId()) {
throw new IllegalArgumentException("Invalid queueId value " + queueId + " (either negative or greater than " + getMaxQueueId());
}
if (itemId < 0 || itemId > getMaxItemId()) {
throw new IllegalArgumentException("Invalid itemId value " + itemId + " (either negative or greater than " + getMaxItemId());
}
byte[] encoded = new byte[queueIdByteLength+itemIdByteLength];
// shift queueId into byte array
for (int i = 0; i < queueIdByteLength; i++) {
encoded[i] = (byte)(queueId >>> ((queueIdByteLength-1-i) * 8));
}
// shift itemId into byte array
for (int i = 0; i < itemIdByteLength; i++) {
encoded[queueIdByteLength+i] = (byte)(itemId >>> ((itemIdByteLength-1-i) * 8));
}
return encoded;
}
public CompositeKey decode(byte[] encoded) {
int queueId = decodeQueueId(encoded);
long itemId = decodeItemId(encoded);
return new CompositeKey(queueId, itemId);
}
public int decodeQueueId(byte[] encoded) {
if (encoded == null) {
throw new NullPointerException("Encoded byte array cannot be null");
}
if (encoded.length != getByteLength()) {
throw new IllegalArgumentException("Encoded byte array must be " + getByteLength() + " bytes in length [actual=" + encoded.length + "]");
}
int queueId = 0;
for (int i = 0; i < queueIdByteLength; i++) {
queueId |= ((encoded[i] & 0xFF) << (queueIdByteLength-1-i) * 8);
}
return queueId;
}
public long decodeItemId(byte[] encoded) {
if (encoded == null) {
throw new NullPointerException("Encoded byte array cannot be null");
}
if (encoded.length != getByteLength()) {
throw new IllegalArgumentException("Encoded byte array must be " + getByteLength() + " bytes in length [actual=" + encoded.length + "]");
}
long itemId = 0;
for (int i = 0; i < itemIdByteLength; i++) {
itemId |= ((encoded[i+queueIdByteLength] & 0xFF) << (itemIdByteLength-1-i) * 8);
}
return itemId;
}
}