package org.juxtapose.streamline.experimental.protocol.message;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import org.apache.commons.lang3.ArrayUtils;
import org.juxtapose.streamline.util.data.DataType;
import org.juxtapose.streamline.util.data.DataTypeBigDecimal;
import org.juxtapose.streamline.util.data.DataTypeBoolean;
import org.juxtapose.streamline.util.data.DataTypeLong;
import org.juxtapose.streamline.util.data.DataTypeNull;
import org.juxtapose.streamline.util.data.DataTypeRef;
import org.juxtapose.streamline.util.data.DataTypeString;
import com.trifork.clj_ds.IPersistentMap;
import com.trifork.clj_ds.PersistentHashMap;
public class DataSerializer
{
static final byte BOOLEAN_TRUE = 1;
static final byte BOOLEAN_FALSE = 2;
static final byte NUMBER_LONG = 3;
static final byte NUMBER_INT = 4;
static final byte NUMBER_SHORT = 5;
static final byte NUMBER_BYTE = 6;
static final byte NUMBER_LONG_NEG = 7;
static final byte NUMBER_INT_NEG = 8;
static final byte NUMBER_SHORT_NEG = 9;
static final byte NUMBER_BYTE_NEG = 10;
static final byte STRING = 11;
static final byte BIG_DEC = 12;
static final byte REF = 13;
static final byte DATA = 14;
static final byte NULL = 15;
public static final int STRING_DESC_BYTE_LENGTH = 4;
public static final int FIELD_BYTE_LENGTH = 4;
public static final int getNumberByteCount( byte inKey )
{
int ret = 0;
switch( inKey )
{
case NUMBER_BYTE_NEG : ret = 1; break;
case NUMBER_BYTE : ret = 1; break;
case NUMBER_SHORT_NEG : ret = 2; break;
case NUMBER_SHORT : ret = 2; break;
case NUMBER_INT_NEG : ret = 4; break;
case NUMBER_INT : ret = 4; break;
case NUMBER_LONG_NEG : ret = 8; break;
case NUMBER_LONG : ret = 8; break;
default:
break;
}
return ret;
}
public static final void main( String... inArg )
{
// IPersistentMap<String, DataType<?>> map = PersistentHashMap.emptyMap();
// map = map.assoc( "1", new DataTypeLong( 12l ) );
// map = map.assoc( "2", new DataTypeBoolean( true ) );
// map = map.assoc( "3", new DataTypeString("hej och h�") );
// map = map.assoc( "4", new DataTypeBoolean( false ) );
// map = map.assoc( "5", new DataTypeLong( 12345l ) );
// map = map.assoc( "1", new DataTypeLong( -123456789l ) );
// map = map.assoc( "6", new DataTypeBigDecimal( new BigDecimal( -0.0023456789, new MathContext( 5, RoundingMode.HALF_EVEN) )));
//
// IPersistentMap<String, DataType<?>> subMap = PersistentHashMap.emptyMap();
// subMap = subMap.assoc( "1", new DataTypeLong( 1l ) );
// subMap = subMap.assoc( "2", new DataTypeBoolean( false ) );
//
// PublishedData pd = new PublishedData(subMap, new HashSet(), null, null, null, 1, true);
// DataTypeRef ref = new DataTypeRef(null, pd);
//
// map = map.assoc( "7", ref );
//
// byte[] bytes = serializeData( map );
//
// map = unSerializeData( bytes );
//
// Iterator<Map.Entry<String,DataType<?>>> iter = map.iterator();
//
// while( iter.hasNext() )
// {
// Map.Entry<String,DataType<?>> entry = iter.next();
// if( entry.getValue() instanceof DataTypeRef )
// {
// Iterator<Map.Entry<String,DataType<?>>> subIter = ((DataTypeRef)entry.getValue()).getReferenceData().getDataMap().iterator();
// while( subIter.hasNext() )
// {
// Map.Entry<String,DataType<?>> subEntry = subIter.next();
// System.out.println("---key: "+subEntry.getKey()+" has value: "+subEntry.getValue());
// }
// }
// else
// System.out.println("key: "+entry.getKey()+" has value: "+entry.getValue());
// }
//
//
// Map<String, String> query = new HashMap<String, String>();
// query.put("CCY1", "SEK");
// query.put("CCY2", "NOK");
// query.put("TYPE", "SP");
// query.put("CPTY", "1");
//
// bytes = serializeQuery( query );
//
// query = unserializeQuery( bytes );
//
// for( Map.Entry<String, String> entry : query.entrySet() )
// {
// System.out.println( entry.getKey()+" = "+entry.getValue() );
// }
}
//BOOLEAN
public static final byte[] serializeBoolean( Integer inField, boolean inBool )
{
byte[] bytes = inBool ? new byte[]{BOOLEAN_TRUE} : new byte[]{ BOOLEAN_FALSE };
return bytes;
}
//Number
public static final byte[] serializeNumber( long number )
{
int sign = Long.signum( number );
byte[] ret = getNumberProperties( number );
if( sign == -1 )
number *= -1;
for( int i = ret.length-1; i > 0; i-- )
{
int shift = (ret.length-1) - (i);
ret[i] = (byte)(number >>> 8 * shift);
}
return ret;
}
public static final byte[] serializeInt( int number )
{
byte[] ret = new byte[4];
for( int i = ret.length-1; i > 0; i-- )
{
int shift = (ret.length-1) - (i);
ret[i] = (byte)(number >>> 8 * shift);
}
return ret;
}
public static long numberFromByteArray( byte[] inBytes, int inOffset, int inLength, Integer inDescriptionByte )
{
long ret = 0;
for(int i = inOffset; i < inOffset+inLength; i++){
ret <<= 8;
ret ^= (long)inBytes[i] & 0xFF;
}
if( inDescriptionByte != null )
{
if( isNegativeNumber( inBytes[0] ) )
ret *= -1;
}
return ret;
}
public static byte[] getByteArrayFrame( byte[] inField, int inLength )
{
byte[] bytes = new byte[ inField.length + inLength ];
System.arraycopy(inField, 0, bytes, 0, inField.length);
return bytes;
}
public static byte[] serializeBigDecimal( byte[] inField, BigDecimal inBigDecimal )
{
//[Field, BIG_DEC, unscaledValue, lengthOfUnscaledValue, scale]
BigInteger theInt = inBigDecimal.unscaledValue();
int scale = inBigDecimal.scale();
byte[] intBytes = theInt.toByteArray();
// byte[] bytes = new byte[ 1 + 4 + intBytes.length + 4 ];
byte[] bytes = getByteArrayFrame(inField, intBytes.length + 9);
bytes[inField.length] = BIG_DEC;
serializeInt( bytes, inField.length+1, intBytes.length );
System.arraycopy( intBytes, 0, bytes, inField.length+5, intBytes.length );
serializeInt( bytes, inField.length+5+intBytes.length, scale);
return bytes;
}
public static byte[] serializeBoolean( byte[] inField, Boolean inBoolean )
{
byte[] bytes = getByteArrayFrame(inField, 1);
bytes[inField.length] = inBoolean ? BOOLEAN_TRUE : BOOLEAN_FALSE;
return bytes;
}
public static byte[] serializeLong( byte[] inField, Long inLong )
{
long number = inLong;
int sign = Long.signum( number );
byte[] numberProps = getNumberProperties( number );
byte[] bytes = getByteArrayFrame(inField, numberProps.length);
bytes[inField.length] = numberProps[0];
if( sign == -1 )
number *= -1;
for( int i = bytes.length-1; i > inField.length; i-- )
{
int shift = (bytes.length-1) - (i);
bytes[i] = (byte)(number >>> 8 * shift);
}
return bytes;
}
public static byte[] serializeRef( byte[] inField, DataTypeRef inRef )
{
byte[] mapBytes = DataSerializer.serializeData( inRef.getReferenceData().getDataMap() );
byte[] bytes = getByteArrayFrame(inField, mapBytes.length+5);
bytes[inField.length] = REF;
serializeInt( bytes, inField.length+1, mapBytes.length );
System.arraycopy( mapBytes, 0, bytes, inField.length+5, mapBytes.length );
return bytes;
}
public static byte[] serializeString( byte[] inField, String inString )
{
byte[] strBytes = inString.getBytes();
byte[] bytes = getByteArrayFrame(inField, strBytes.length+5);
bytes[inField.length] = STRING;
serializeInt( bytes, inField.length+1, strBytes.length );
System.arraycopy( strBytes, 0, bytes, inField.length+5, strBytes.length );
return bytes;
}
public static final byte[] serializeNull( byte[] inField )
{
byte[] bytes = getByteArrayFrame(inField, 1);
bytes[inField.length] = NULL;
return bytes;
}
/**
* @param inField
* @param inDataEntry
* @return
*/
public static final byte[] serializeDataEntry( byte[] inField, DataType<?> inDataEntry )
{
if( inDataEntry instanceof DataTypeBigDecimal )
{
return serializeBigDecimal(inField, ((DataTypeBigDecimal)inDataEntry).get() );
}
else if( inDataEntry instanceof DataTypeBoolean )
{
return serializeBoolean( inField, ((DataTypeBoolean)inDataEntry).get() );
}
else if( inDataEntry instanceof DataTypeLong )
{
return serializeLong( inField, ((DataTypeLong)inDataEntry).get() );
}
else if( inDataEntry instanceof DataTypeRef )
{
return serializeRef( inField, (DataTypeRef)inDataEntry );
}
else if( inDataEntry instanceof DataTypeString )
{
return serializeString( inField, ((DataTypeString)inDataEntry).get() );
}
else if( inDataEntry instanceof DataTypeNull )
{
return serializeNull( inField );
}
else
{
throw new IllegalArgumentException(" No serialization for "+inDataEntry.getClass() );
}
}
/**
* @param inData
* @return
*/
public static final byte[] serializeData( IPersistentMap<String, DataType<?>> inData )
{
Iterator<Map.Entry<String,DataType<?>>> iter = inData.iterator();
byte[][] byteArrays = new byte[inData.count()][];
int i = 0;
int totalBytes = 0;
while( iter.hasNext() )
{
Map.Entry<String,DataType<?>> entry = iter.next();
byte[] fieldBytes = serializeString( entry.getKey() );
byte[] bytes = serializeDataEntry( fieldBytes, entry.getValue() );
byteArrays[ i ] = bytes;
i++;
totalBytes += bytes.length;
}
byte[] bytes = new byte[totalBytes];
int offSet = 0;
for( byte[] byteArr : byteArrays )
{
System.arraycopy( byteArr, 0, bytes, offSet, byteArr.length );
offSet+=byteArr.length;
}
return bytes;
}
public static final IPersistentMap<String, DataType<?>> unSerializeData( byte[] inBytes )
{
IPersistentMap<String, DataType<?>> map = PersistentHashMap.emptyMap();
int cursor = 0;
do
{
int fieldLength = (int)numberFromByteArray( inBytes, cursor, FIELD_BYTE_LENGTH, null );
cursor+=4;
byte[] fieldCharBytes = ArrayUtils.subarray( inBytes, cursor, cursor+fieldLength );
String field = new String(fieldCharBytes);
cursor += fieldCharBytes.length;
if( inBytes[cursor] == BOOLEAN_TRUE )
{
map = map.assoc( field, new DataTypeBoolean( true ) );
cursor++;
}
else if( inBytes[cursor] == BOOLEAN_FALSE )
{
map = map.assoc( field, new DataTypeBoolean( false ) );
cursor++;
}
else if( isNumber( inBytes[cursor] ))
{
int length = getNumberByteCount( inBytes[cursor] );
long number = numberFromByteArray( inBytes, cursor+1, length, cursor );
cursor+= length+1;
map = map.assoc( field, new DataTypeLong( number ));
}
else if( inBytes[cursor] == STRING )
{
cursor++;
int stringLenght = (int)numberFromByteArray( inBytes, cursor, STRING_DESC_BYTE_LENGTH, null );
cursor+= STRING_DESC_BYTE_LENGTH;
byte[] charBytes = ArrayUtils.subarray( inBytes, cursor, cursor+stringLenght );
String str = new String(charBytes);
cursor += charBytes.length;
map = map.assoc( field, new DataTypeString( str ) );
}
else if( inBytes[cursor] == BIG_DEC )
{
cursor++;
int intLenght = (int)numberFromByteArray( inBytes, cursor, 4, null );
cursor+= 4;
byte[] bdBytes = ArrayUtils.subarray( inBytes, cursor, cursor+intLenght );
BigInteger bi = new BigInteger(bdBytes);
cursor += bdBytes.length;
int scale = (int)numberFromByteArray( inBytes, cursor, 4, null );
DataTypeBigDecimal bd = new DataTypeBigDecimal( new BigDecimal(bi, scale) );
cursor += 4;
map = map.assoc( field, bd );
}
else if( inBytes[cursor] == REF )
{
cursor++;
int mapLenght = (int)numberFromByteArray( inBytes, cursor, 4, null );
cursor+= 4;
byte[] mapBytes = ArrayUtils.subarray( inBytes, cursor, cursor+mapLenght );
IPersistentMap<String, DataType<?>> subMap = unSerializeData( mapBytes );
// PublishedData pd = new PublishedData(subMap, new HashSet(), null, null, null, 1, true);
//
// DataTypeRef ref = new DataTypeRef(null, pd);
//
// map = map.assoc( field, ref );
cursor+= mapBytes.length;
}
}
while( cursor < inBytes.length );
return map;
}
public static final byte[] serializeQuery( Map<String, String> inQuery )
{
byte[] bytes = null;
int offset = 0;
for( Map.Entry<String, String> entry : inQuery.entrySet() )
{
byte[] key = serializeString(entry.getKey());
byte[] value = serializeString(entry.getValue());
if( bytes == null )
bytes = new byte[key.length + value.length];
else
{
byte[] newBytes = new byte[bytes.length+key.length+value.length];
System.arraycopy(bytes, 0, newBytes, 0, bytes.length);
bytes = newBytes;
}
System.arraycopy( key, 0, bytes, offset, key.length);
offset += key.length;
System.arraycopy( value, 0, bytes, offset, value.length);
offset+= value.length;
}
return bytes;
}
public static final Map<String, String> unserializeQuery( byte[] inQueryBytes )
{
HashMap<String, String> queryMap = new HashMap<String, String>();
int offset = 0;
if( inQueryBytes == null || inQueryBytes.length < 4 )
return queryMap;
do
{
int keyLength = (int)numberFromByteArray( inQueryBytes, offset, FIELD_BYTE_LENGTH, null );
offset += FIELD_BYTE_LENGTH;
byte[] charBytes = ArrayUtils.subarray( inQueryBytes, offset, offset+keyLength );
String key = new String(charBytes);
offset += charBytes.length;
int valueLength = (int)numberFromByteArray( inQueryBytes, offset, FIELD_BYTE_LENGTH, null );
offset += FIELD_BYTE_LENGTH;
byte[] valueCharBytes = ArrayUtils.subarray( inQueryBytes, offset, offset+valueLength );
String value = new String( valueCharBytes );
offset += valueCharBytes.length;
queryMap.put( key, value );
}
while( offset < inQueryBytes.length );
return queryMap;
}
/**
* @param inString
* @return
*/
public static final byte[] serializeString( String inString )
{
assert (inString.length() < Integer.MAX_VALUE ) : "String is to big.. larger than Integer.MAX_VALUE";
byte[] strBytes = inString.getBytes();
byte[] bytes = new byte[strBytes.length+4];
serializeInt(bytes, 0, strBytes.length);
System.arraycopy(strBytes, 0, bytes, 4, strBytes.length);
return bytes;
}
/**
* @param inBytes
* @param inOffSet
* @param number
* @return
*/
public static final byte[] serializeInt( byte[] inBytes, int inOffSet, int number )
{
assert (inBytes.length - inOffSet) <= 4 : "integer will not fit into byte array from offset";
for( int i = 3; i >= 0; i-- )
{
int shift = (3) - (i);
inBytes[inOffSet + i] = (byte)(number >>> 8 * shift);
}
return inBytes;
}
/**
* @param inNumber
* @return
*/
public static final byte[] getNumberProperties( long inNumber )
{
if( inNumber < Integer.MIN_VALUE )
{
byte[] ret = new byte[9];
ret[0] = NUMBER_LONG_NEG;
return ret;
}
else if( inNumber < Short.MIN_VALUE )
{
byte[] ret = new byte[5];
ret[0] = NUMBER_INT_NEG;
return ret;
}
else if( inNumber < Byte.MIN_VALUE )
{
byte[] ret = new byte[3];
ret[0] = NUMBER_SHORT_NEG;
return ret;
}
else if( inNumber < 0 )
{
byte[] ret = new byte[2];
ret[0] = NUMBER_BYTE_NEG;
return ret;
}
else if( inNumber < Byte.MAX_VALUE )
{
byte[] ret = new byte[2];
ret[0] = NUMBER_BYTE;
return ret;
}
else if( inNumber < Short.MAX_VALUE )
{
byte[] ret = new byte[3];
ret[0] = NUMBER_SHORT;
return ret;
}
else if( inNumber < Integer.MAX_VALUE )
{
byte[] ret = new byte[5];
ret[0] = NUMBER_INT;
return ret;
}
else
{
byte[] ret = new byte[9];
ret[0] = NUMBER_LONG;
return ret;
}
}
/**
* @param inByte
* @return
*/
public static final boolean isNumber( byte inByte )
{
if( inByte == NUMBER_BYTE || inByte == NUMBER_SHORT || inByte == NUMBER_INT || inByte == NUMBER_LONG || isNegativeNumber( inByte ) )
{
return true;
}
return false;
}
public static final boolean isNegativeNumber( byte inByte )
{
if( inByte == NUMBER_BYTE_NEG || inByte == NUMBER_SHORT_NEG || inByte == NUMBER_INT_NEG || inByte == NUMBER_LONG_NEG )
{
return true;
}
return false;
}
}