package com.github.czyzby.websocket.serialization.impl;
import com.github.czyzby.websocket.serialization.SerializationException;
/** Contains number sizes handled by the {@link Serializer} and {@link Deserializer}. Allows to convert different number
* sizes for the needs of serialization.
*
* @author MJ */
public enum Size {
/** Uses 1 byte to serialize numbers. Truncates shorts, ints and longs. Cannot serialize floats or doubles. As array
* length size, can serialize array lengths up to {@link java.lang.Byte#MAX_VALUE}. */
BYTE(1, Byte.MAX_VALUE) {
@Override
void serializeShort(final short value, final Serializer serializer) {
serializer.serializedData[serializer.currentByteArrayIndex++] = (byte) value;
}
@Override
void serializeInt(final int value, final Serializer serializer) {
serializer.serializedData[serializer.currentByteArrayIndex++] = (byte) value;
}
@Override
void serializeLong(final long value, final Serializer serializer) {
serializer.serializedData[serializer.currentByteArrayIndex++] = (byte) value;
}
@Override
void serializeFloat(final float value, final Serializer serializer) throws SerializationException {
throw new SerializationException("Byte is too small to store float data. Cannot truncate.");
}
@Override
void serializeDouble(final double value, final Serializer serializer) throws SerializationException {
throw new SerializationException("Byte is too small to store double data. Cannot truncate");
}
@Override
short deserializeShort(final Deserializer deserializer) {
return deserializeByte(deserializer);
}
@Override
int deserializeInt(final Deserializer deserializer) {
return deserializeByte(deserializer);
}
@Override
long deserializeLong(final Deserializer deserializer) {
return deserializeByte(deserializer);
}
@Override
float deserializeFloat(final Deserializer deserializer) throws SerializationException {
throw new SerializationException("Byte is too small to store float data. Cannot deserialize.");
}
@Override
double deserializeDouble(final Deserializer deserializer) throws SerializationException {
throw new SerializationException("Byte is too small to store double data. Cannot deserialize.");
}
},
/** Uses 2 bytes to serialize numbers. Uses 1 byte to serialize bytes and booleans. Truncates ints and longs. Cannot
* serialize floats or doubles. As array length size, can serialize array lengths up to
* {@link java.lang.Short#MAX_VALUE}. */
SHORT(2, Short.MAX_VALUE) {
@Override
void serializeInt(final int value, final Serializer serializer) {
serializer.serializedData[serializer.currentByteArrayIndex++] = (byte) (value >>> 8);
serializer.serializedData[serializer.currentByteArrayIndex++] = (byte) value;
}
@Override
void serializeLong(final long value, final Serializer serializer) {
serializer.serializedData[serializer.currentByteArrayIndex++] = (byte) (value >>> 8);
serializer.serializedData[serializer.currentByteArrayIndex++] = (byte) value;
}
@Override
void serializeFloat(final float value, final Serializer serializer) throws SerializationException {
throw new SerializationException("Short is too small to store float data. Cannot truncate.");
}
@Override
void serializeDouble(final double value, final Serializer serializer) throws SerializationException {
throw new SerializationException("Short is too small to store double data. Cannot truncate");
}
@Override
int deserializeInt(final Deserializer deserializer) {
return deserializeShort(deserializer);
}
@Override
long deserializeLong(final Deserializer deserializer) {
return deserializeShort(deserializer);
}
@Override
float deserializeFloat(final Deserializer deserializer) throws SerializationException {
throw new SerializationException("Short is too small to store float data. Cannot deserialize.");
}
@Override
double deserializeDouble(final Deserializer deserializer) throws SerializationException {
throw new SerializationException("Short is too small to store double data. Cannot deserialize.");
}
},
/** Uses 4 bytes to serialize numbers. Uses 2 bytes to serialize shorts and 1 byte to serialize bytes and booleans.
* Truncates longs and doubles. As array length size, can serialize array lengths up to
* {@link java.lang.Integer#MAX_VALUE}. */
INT(4, Integer.MAX_VALUE) {
@Override
void serializeLong(final long value, final Serializer serializer) {
serializer.serializedData[serializer.currentByteArrayIndex++] = (byte) (value >>> 24);
serializer.serializedData[serializer.currentByteArrayIndex++] = (byte) (value >>> 16);
serializer.serializedData[serializer.currentByteArrayIndex++] = (byte) (value >>> 8);
serializer.serializedData[serializer.currentByteArrayIndex++] = (byte) value;
}
@Override
void serializeFloat(final float value, final Serializer serializer) {
serializeInt(Float.floatToIntBits(value), serializer);
}
@Override
void serializeDouble(final double value, final Serializer serializer) {
serializeFloat((float) value, serializer);
}
@Override
long deserializeLong(final Deserializer deserializer) {
return deserializeInt(deserializer);
}
@Override
float deserializeFloat(final Deserializer deserializer) throws SerializationException {
return Float.intBitsToFloat(deserializeInt(deserializer));
}
@Override
double deserializeDouble(final Deserializer deserializer) throws SerializationException {
return deserializeFloat(deserializer);
}
},
/** Uses 8 bytes to serialize numbers. Uses 4 bytes to serialize ints, 2 bytes to serialize shorts and 1 byte to
* serialize bytes and booleans. Expands floats to doubles, potentially allowing to store more exact data than
* floats serialized as ints. As array length size, can serialize array lengths up to
* {@link java.lang.Integer#MAX_VALUE} - since Java uses ints as array length indexes, there's no point in
* serializing array lengths as longs. */
LONG(8, Integer.MAX_VALUE) {
@Override
void serializeDouble(final double value, final Serializer serializer) {
serializeLong(Double.doubleToLongBits(value), serializer);
}
@Override
void serializeFloat(final float value, final Serializer serializer) {
serializeDouble(value, serializer);
}
@Override
float deserializeFloat(final Deserializer deserializer) {
return (float) deserializeDouble(deserializer);
}
@Override
double deserializeDouble(final Deserializer deserializer) {
return Double.longBitsToDouble(deserializeLong(deserializer));
}
};
static final int NULL_ARRAY_ID = -1;
private final int bytesAmount;
private final int maxArrayLength;
private Size(final int bytesAmount, final int maxArrayLength) {
this.bytesAmount = bytesAmount;
this.maxArrayLength = maxArrayLength;
}
/** @return smallest number size big enough to store all possible array lengths in Java: {@link #INT}. */
public static Size getDefaultArrayLengthSize() {
return Size.INT;
}
/** @return bytes amount needed to serialize the number. */
public int getBytesAmount() {
return bytesAmount;
}
/** @return when using this number size for serializing array lengths, this is the maximum value of array length
* that can be stored with this size's bytes amount. */
public int getMaxArrayLength() {
return maxArrayLength;
}
// Number serialization methods:
void serializeBoolean(final boolean value, final Serializer serializer) {
if (value) {
serializer.serializedData[serializer.currentByteArrayIndex++] = 1;
} else {
serializer.serializedData[serializer.currentByteArrayIndex++] = 0;
}
}
void serializeByte(final byte value, final Serializer serializer) {
serializer.serializedData[serializer.currentByteArrayIndex++] = value;
}
void serializeShort(final short value, final Serializer serializer) {
serializer.serializedData[serializer.currentByteArrayIndex++] = (byte) (value >>> 8);
serializer.serializedData[serializer.currentByteArrayIndex++] = (byte) value;
}
void serializeInt(final int value, final Serializer serializer) {
serializer.serializedData[serializer.currentByteArrayIndex++] = (byte) (value >>> 24);
serializer.serializedData[serializer.currentByteArrayIndex++] = (byte) (value >>> 16);
serializer.serializedData[serializer.currentByteArrayIndex++] = (byte) (value >>> 8);
serializer.serializedData[serializer.currentByteArrayIndex++] = (byte) value;
}
void serializeLong(final long value, final Serializer serializer) {
serializer.serializedData[serializer.currentByteArrayIndex++] = (byte) (value >>> 56);
serializer.serializedData[serializer.currentByteArrayIndex++] = (byte) (value >>> 48);
serializer.serializedData[serializer.currentByteArrayIndex++] = (byte) (value >>> 40);
serializer.serializedData[serializer.currentByteArrayIndex++] = (byte) (value >>> 32);
serializer.serializedData[serializer.currentByteArrayIndex++] = (byte) (value >>> 24);
serializer.serializedData[serializer.currentByteArrayIndex++] = (byte) (value >>> 16);
serializer.serializedData[serializer.currentByteArrayIndex++] = (byte) (value >>> 8);
serializer.serializedData[serializer.currentByteArrayIndex++] = (byte) value;
}
abstract void serializeFloat(final float value, final Serializer serializer) throws SerializationException;
abstract void serializeDouble(final double value, final Serializer serializer) throws SerializationException;
// Array serialization methods:
/** @param arrayLength array length to serialize with the selected size.
* @throws SerializationException if array length is too big. */
protected void validateArrayLengthToSerialize(final int arrayLength) throws SerializationException {
if (maxArrayLength < arrayLength) {
throw new SerializationException(name() + " number size is to small to store array length of: "
+ arrayLength + ". Data would be lost upon serialization.");
}
}
private void prepareArray(final Size arrayLengthSize, final Serializer serializer, final int arrayLength)
throws SerializationException {
// Checking if length is smaller than the max value that can be serialized with this number size:
arrayLengthSize.validateArrayLengthToSerialize(arrayLength);
// Ensuring capacity for serializing array length and each element:
// (This might oversize the serialized data array, but only if the user requested to serialize the
// array with number size bigger than the actual numbers - so that's basically programmer's mistake.)
serializer.ensureCapacity(arrayLengthSize.bytesAmount + arrayLength * bytesAmount);
// Serializing array length:
serializer.serializeInt(arrayLength, arrayLengthSize);
}
void serializeBooleanArray(final boolean[] array, final Size arrayLengthSize, final Serializer serializer)
throws SerializationException {
if (array == null) {
serializer.serializeInt(NULL_ARRAY_ID, arrayLengthSize);
return;
}
prepareArray(arrayLengthSize, serializer, array.length);
for (final boolean value : array) {
serializeBoolean(value, serializer);
}
}
void serializeByteArray(final byte[] array, final Size arrayLengthSize, final Serializer serializer)
throws SerializationException {
if (array == null) {
serializer.serializeInt(NULL_ARRAY_ID, arrayLengthSize);
return;
}
final int arrayLength = array.length;
prepareArray(arrayLengthSize, serializer, arrayLength);
if (arrayLength > 0) {
System.arraycopy(array, 0, serializer.serializedData, serializer.currentByteArrayIndex, arrayLength);
serializer.currentByteArrayIndex += arrayLength;
}
}
void serializeShortArray(final short[] array, final Size arrayLengthSize, final Serializer serializer)
throws SerializationException {
if (array == null) {
serializer.serializeInt(NULL_ARRAY_ID, arrayLengthSize);
return;
}
prepareArray(arrayLengthSize, serializer, array.length);
for (final short value : array) {
serializeShort(value, serializer);
}
}
void serializeIntArray(final int[] array, final Size arrayLengthSize, final Serializer serializer)
throws SerializationException {
if (array == null) {
serializer.serializeInt(NULL_ARRAY_ID, arrayLengthSize);
return;
}
prepareArray(arrayLengthSize, serializer, array.length);
for (final int value : array) {
serializeInt(value, serializer);
}
}
void serializeLongArray(final long[] array, final Size arrayLengthSize, final Serializer serializer)
throws SerializationException {
if (array == null) {
serializer.serializeInt(NULL_ARRAY_ID, arrayLengthSize);
return;
}
prepareArray(arrayLengthSize, serializer, array.length);
for (final long value : array) {
serializeLong(value, serializer);
}
}
void serializeFloatArray(final float[] array, final Size arrayLengthSize, final Serializer serializer)
throws SerializationException {
if (array == null) {
serializer.serializeInt(NULL_ARRAY_ID, arrayLengthSize);
return;
}
prepareArray(arrayLengthSize, serializer, array.length);
for (final float value : array) {
serializeFloat(value, serializer);
}
}
void serializeDoubleArray(final double[] array, final Size arrayLengthSize, final Serializer serializer)
throws SerializationException {
if (array == null) {
serializer.serializeInt(NULL_ARRAY_ID, arrayLengthSize);
return;
}
prepareArray(arrayLengthSize, serializer, array.length);
for (final double value : array) {
serializeDouble(value, serializer);
}
}
// Number deserialization methods:
boolean deserializeBoolean(final Deserializer deserializer) {
return deserializer.serializedData[deserializer.currentByteArrayIndex++] > 0;
}
byte deserializeByte(final Deserializer deserializer) {
return deserializer.serializedData[deserializer.currentByteArrayIndex++];
}
short deserializeShort(final Deserializer deserializer) {
return (short) ((deserializer.serializedData[deserializer.currentByteArrayIndex++] & 0xFF) << 8
| deserializer.serializedData[deserializer.currentByteArrayIndex++] & 0xFF);
}
int deserializeInt(final Deserializer deserializer) {
return deserializer.serializedData[deserializer.currentByteArrayIndex++] << 24
| (deserializer.serializedData[deserializer.currentByteArrayIndex++] & 0xFF) << 16
| (deserializer.serializedData[deserializer.currentByteArrayIndex++] & 0xFF) << 8
| deserializer.serializedData[deserializer.currentByteArrayIndex++] & 0xFF;
}
long deserializeLong(final Deserializer deserializer) {
return (long) deserializer.serializedData[deserializer.currentByteArrayIndex++] << 56
| (long) (deserializer.serializedData[deserializer.currentByteArrayIndex++] & 0xFF) << 48
| (long) (deserializer.serializedData[deserializer.currentByteArrayIndex++] & 0xFF) << 40
| (long) (deserializer.serializedData[deserializer.currentByteArrayIndex++] & 0xFF) << 32
| (long) (deserializer.serializedData[deserializer.currentByteArrayIndex++] & 0xFF) << 24
| (deserializer.serializedData[deserializer.currentByteArrayIndex++] & 0xFF) << 16
| (deserializer.serializedData[deserializer.currentByteArrayIndex++] & 0xFF) << 8
| deserializer.serializedData[deserializer.currentByteArrayIndex++] & 0xFF;
}
abstract float deserializeFloat(Deserializer deserializer) throws SerializationException;
abstract double deserializeDouble(Deserializer deserializer) throws SerializationException;
// Array deserialization methods:
private static void validateArrayLengthToCreate(final int arraySize) throws SerializationException {
if (arraySize < 0) {
throw new SerializationException("Cannot deserialize array. Negative array length: " + arraySize);
}
}
boolean[] deserializeBooleanArray(final Deserializer deserializer, final Size arrayLengthSize)
throws SerializationException {
final int arraySize = deserializer.deserializeInt(arrayLengthSize);
if (arraySize == NULL_ARRAY_ID) {
return null;
} else if (arraySize == 0) {
return EmptyArrays.BOOLEAN;
}
validateArrayLengthToCreate(arraySize);
deserializer.validateBytesAmountToDeserialize(arraySize);
final boolean[] array = new boolean[arraySize];
for (int index = 0; index < arraySize; index++) {
array[index] = deserializeBoolean(deserializer);
}
return array;
}
int deserializeBooleanArray(final Deserializer deserializer, final Size arrayLengthSize, final boolean[] result)
throws SerializationException {
final int arraySize = deserializer.deserializeInt(arrayLengthSize);
if (arraySize <= 0) {
return 0;
}
deserializer.validateBytesAmountToDeserialize(arraySize);
validateCachedArrayLength(result.length, arraySize);
for (int index = 0; index < arraySize; index++) {
result[index] = deserializeBoolean(deserializer);
}
return arraySize;
}
static void validateCachedArrayLength(final int resultArrayLength, final int arraySize)
throws SerializationException {
if (resultArrayLength < arraySize) {
throw new SerializationException("Passed array has is too small to contain serialized array data. Length: "
+ resultArrayLength + ", required: " + arraySize);
}
}
byte[] deserializeByteArray(final Deserializer deserializer, final Size arrayLengthSize)
throws SerializationException {
final int arraySize = deserializer.deserializeInt(arrayLengthSize);
if (arraySize == NULL_ARRAY_ID) {
return null;
}
if (arraySize == 0) {
return EmptyArrays.BYTE;
}
validateArrayLengthToCreate(arraySize);
deserializer.validateBytesAmountToDeserialize(arraySize);
final byte[] array = new byte[arraySize];
System.arraycopy(deserializer.serializedData, deserializer.currentByteArrayIndex, array, 0, arraySize);
deserializer.currentByteArrayIndex += arraySize;
return array;
}
int deserializeByteArray(final Deserializer deserializer, final Size arrayLengthSize, final byte[] result)
throws SerializationException {
final int arraySize = deserializer.deserializeInt(arrayLengthSize);
if (arraySize <= 0) {
return 0;
}
deserializer.validateBytesAmountToDeserialize(arraySize);
validateCachedArrayLength(result.length, arraySize);
System.arraycopy(deserializer.serializedData, deserializer.currentByteArrayIndex, result, 0, arraySize);
deserializer.currentByteArrayIndex += arraySize;
return arraySize;
}
short[] deserializeShortArray(final Deserializer deserializer, final Size arrayLengthSize)
throws SerializationException {
final int arraySize = deserializer.deserializeInt(arrayLengthSize);
if (arraySize == NULL_ARRAY_ID) {
return null;
}
if (arraySize == 0) {
return EmptyArrays.SHORT;
}
validateArrayLengthToCreate(arraySize);
deserializer.validateBytesAmountToDeserialize(arraySize * Math.min(bytesAmount, SHORT.bytesAmount));
final short[] array = new short[arraySize];
for (int index = 0; index < arraySize; index++) {
array[index] = deserializeShort(deserializer);
}
return array;
}
int deserializeShortArray(final Deserializer deserializer, final Size arrayLengthSize, final short[] result)
throws SerializationException {
final int arraySize = deserializer.deserializeInt(arrayLengthSize);
if (arraySize <= 0) {
return 0;
}
deserializer.validateBytesAmountToDeserialize(arraySize * Math.min(bytesAmount, SHORT.bytesAmount));
validateCachedArrayLength(result.length, arraySize);
for (int index = 0; index < arraySize; index++) {
result[index] = deserializeShort(deserializer);
}
return arraySize;
}
int[] deserializeIntArray(final Deserializer deserializer, final Size arrayLengthSize)
throws SerializationException {
final int arraySize = deserializer.deserializeInt(arrayLengthSize);
if (arraySize == NULL_ARRAY_ID) {
return null;
}
if (arraySize == 0) {
return EmptyArrays.INT;
}
validateArrayLengthToCreate(arraySize);
deserializer.validateBytesAmountToDeserialize(arraySize * Math.min(bytesAmount, INT.bytesAmount));
final int[] array = new int[arraySize];
for (int index = 0; index < arraySize; index++) {
array[index] = deserializeInt(deserializer);
}
return array;
}
int deserializeIntArray(final Deserializer deserializer, final Size arrayLengthSize, final int[] result)
throws SerializationException {
final int arraySize = deserializer.deserializeInt(arrayLengthSize);
if (arraySize <= 0) {
return 0;
}
deserializer.validateBytesAmountToDeserialize(arraySize * Math.min(bytesAmount, INT.bytesAmount));
validateCachedArrayLength(result.length, arraySize);
for (int index = 0; index < arraySize; index++) {
result[index] = deserializeInt(deserializer);
}
return arraySize;
}
long[] deserializeLongArray(final Deserializer deserializer, final Size arrayLengthSize)
throws SerializationException {
final int arraySize = deserializer.deserializeInt(arrayLengthSize);
if (arraySize == NULL_ARRAY_ID) {
return null;
}
if (arraySize == 0) {
return EmptyArrays.LONG;
}
validateArrayLengthToCreate(arraySize);
deserializer.validateBytesAmountToDeserialize(arraySize * bytesAmount);
final long[] array = new long[arraySize];
for (int index = 0; index < arraySize; index++) {
array[index] = deserializeLong(deserializer);
}
return array;
}
int deserializeLongArray(final Deserializer deserializer, final Size arrayLengthSize, final long[] result)
throws SerializationException {
final int arraySize = deserializer.deserializeInt(arrayLengthSize);
if (arraySize <= 0) {
return 0;
}
deserializer.validateBytesAmountToDeserialize(arraySize * bytesAmount);
validateCachedArrayLength(result.length, arraySize);
for (int index = 0; index < arraySize; index++) {
result[index] = deserializeLong(deserializer);
}
return arraySize;
}
float[] deserializeFloatArray(final Deserializer deserializer, final Size arrayLengthSize)
throws SerializationException {
final int arraySize = deserializer.deserializeInt(arrayLengthSize);
if (arraySize == NULL_ARRAY_ID) {
return null;
}
if (arraySize == 0) {
return EmptyArrays.FLOAT;
}
validateArrayLengthToCreate(arraySize);
deserializer.validateBytesAmountToDeserialize(arraySize * bytesAmount);
final float[] array = new float[arraySize];
for (int index = 0; index < arraySize; index++) {
array[index] = deserializeFloat(deserializer);
}
return array;
}
int deserializeFloatArray(final Deserializer deserializer, final Size arrayLengthSize, final float[] result)
throws SerializationException {
final int arraySize = deserializer.deserializeInt(arrayLengthSize);
if (arraySize <= 0) {
return 0;
}
deserializer.validateBytesAmountToDeserialize(arraySize * bytesAmount);
validateCachedArrayLength(result.length, arraySize);
for (int index = 0; index < arraySize; index++) {
result[index] = deserializeFloat(deserializer);
}
return arraySize;
}
double[] deserializeDoubleArray(final Deserializer deserializer, final Size arrayLengthSize)
throws SerializationException {
final int arraySize = deserializer.deserializeInt(arrayLengthSize);
if (arraySize == NULL_ARRAY_ID) {
return null;
}
if (arraySize == 0) {
return EmptyArrays.DOUBLE;
}
validateArrayLengthToCreate(arraySize);
deserializer.validateBytesAmountToDeserialize(arraySize * bytesAmount);
final double[] array = new double[arraySize];
for (int index = 0; index < arraySize; index++) {
array[index] = deserializeDouble(deserializer);
}
return array;
}
int deserializeDoubleArray(final Deserializer deserializer, final Size arrayLengthSize, final double[] result)
throws SerializationException {
final int arraySize = deserializer.deserializeInt(arrayLengthSize);
if (arraySize <= 0) {
return 0;
}
deserializer.validateBytesAmountToDeserialize(arraySize * bytesAmount);
validateCachedArrayLength(result.length, arraySize);
for (int index = 0; index < arraySize; index++) {
result[index] = deserializeDouble(deserializer);
}
return arraySize;
}
}