/**
* Copyright (c) 2008 Greg Whalin
* All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the BSD license
*
* This library is distributed in the hope that it will be
* useful, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE.
*
* You should have received a copy of the BSD License along with this
* library.
*
* @author Greg Whalin <greg@meetup.com>
*/
package com.meetup.memcached;
import java.util.Date;
import org.apache.commons.lang.CharEncoding;
import org.apache.log4j.Logger;
/**
* Handle encoding standard Java types directly which can result in significant
* memory savings:
*
* Currently the Memcached driver for Java supports the setSerialize() option.
* This can increase performance in some situations but has a few issues:
*
* Code that performs class casting will throw ClassCastExceptions when
* setSerialize is enabled. For example:
*
* mc.set( "foo", Integer.valueOf( 1 ) ); Integer output = (Integer)mc.get("foo");
*
* Will work just file when setSerialize is true but when its false will just throw
* a ClassCastException.
*
* Also internally it doesn't support Boolean and since toString is called wastes a
* lot of memory and causes additional performance issue. For example an Integer
* can take anywhere from 1 byte to 10 bytes.
*
* Due to the way the memcached slab allocator works it seems like a LOT of wasted
* memory to store primitive types as serialized objects (from a performance and
* memory perspective). In our applications we have millions of small objects and
* wasted memory would become a big problem.
*
* For example a Serialized Boolean takes 47 bytes which means it will fit into the
* 64byte LRU. Using 1 byte means it will fit into the 8 byte LRU thus saving 8x
* the memory. This also saves the CPU performance since we don't have to
* serialize bytes back and forth and we can compute the byte[] value directly.
*
* One problem would be when the user calls get() because doing so would require
* the app to know the type of the object stored as a bytearray inside memcached
* (since the user will probably cast).
*
* If we assume the basic types are interned we could use the first byte as the
* type with the remaining bytes as the value. Then on get() we could read the
* first byte to determine the type and then construct the correct object for it.
* This would prevent the ClassCastException I talked about above.
*
* We could remove the setSerialize() option and just assume that standard VM types
* are always internd in this manner.
*
* mc.set( "foo", new Boolean.TRUE ); Boolean b = (Boolean)mc.get( "foo" );
*
* And the type casts would work because internally we would create a new Boolean
* to return back to the client.
*
* This would reduce memory footprint and allow for a virtual implementation of the
* Externalizable interface which is much faster than Serialzation.
*
* Currently the memory improvements would be:
*
* java.lang.Boolean - 8x performance improvement (now just two bytes)
* java.lang.Integer - 16x performance improvement (now just 5 bytes)
*
* Most of the other primitive types would benefit from this optimization.
* java.lang.Character being another obvious example.
*
* I know it seems like I'm being really picky here but for our application I'd
* save 1G of memory right off the bat. We'd go down from 1.152G of memory used
* down to 144M of memory used which is much better IMO.
*
* http://java.sun.com/docs/books/tutorial/native1.1/integrating/types.html
*
* @author <a href="mailto:burton@peerfear.org">Kevin A. Burton</a>
* @author Greg Whalin <greg@meetup.com>
*/
public class NativeHandler {
// logger
private static Logger log =
Logger.getLogger( NativeHandler.class.getName() );
/**
* Detemine of object can be natively serialized by this class.
*
* @param value Object to test.
* @return true/false
*/
public static boolean isHandled( Object value ) {
return (
value instanceof Byte ||
value instanceof Boolean ||
value instanceof Integer ||
value instanceof Long ||
value instanceof Character ||
value instanceof String ||
value instanceof StringBuffer ||
value instanceof Float ||
value instanceof Short ||
value instanceof Double ||
value instanceof Date ||
value instanceof StringBuilder ||
value instanceof byte[]
)
? true
: false;
}
/**
* Returns the flag for marking the type of the byte array.
*
* @param value Object we are storing.
* @return int marker
*/
public static int getMarkerFlag( Object value ) {
if ( value instanceof Byte )
return MemcachedClient.MARKER_BYTE;
if ( value instanceof Boolean )
return MemcachedClient.MARKER_BOOLEAN;
if ( value instanceof Integer )
return MemcachedClient.MARKER_INTEGER;
if ( value instanceof Long )
return MemcachedClient.MARKER_LONG;
if ( value instanceof Character )
return MemcachedClient.MARKER_CHARACTER;
if ( value instanceof String )
return MemcachedClient.MARKER_STRING;
if ( value instanceof StringBuffer )
return MemcachedClient.MARKER_STRINGBUFFER;
if ( value instanceof Float )
return MemcachedClient.MARKER_FLOAT;
if ( value instanceof Short )
return MemcachedClient.MARKER_SHORT;
if ( value instanceof Double )
return MemcachedClient.MARKER_DOUBLE;
if ( value instanceof Date )
return MemcachedClient.MARKER_DATE;
if ( value instanceof StringBuilder )
return MemcachedClient.MARKER_STRINGBUILDER;
if ( value instanceof byte[] )
return MemcachedClient.MARKER_BYTEARR;
return -1;
}
/**
* Encodes supported types
*
* @param value Object to encode.
* @return byte array
*
* @throws Exception If fail to encode.
*/
public static byte[] encode( Object value ) throws Exception {
if ( value instanceof Byte )
return encode( (Byte)value );
if ( value instanceof Boolean )
return encode( (Boolean)value );
if ( value instanceof Integer )
return encode( ((Integer)value).intValue() );
if ( value instanceof Long )
return encode( ((Long)value).longValue() );
if ( value instanceof Character )
return encode( (Character)value );
if ( value instanceof String )
return encode( (String)value );
if ( value instanceof StringBuffer )
return encode( (StringBuffer)value );
if ( value instanceof Float )
return encode( ((Float)value).floatValue() );
if ( value instanceof Short )
return encode( (Short)value );
if ( value instanceof Double )
return encode( ((Double)value).doubleValue() );
if ( value instanceof Date )
return encode( (Date)value);
if ( value instanceof StringBuilder )
return encode( (StringBuilder)value );
if ( value instanceof byte[] )
return encode( (byte[])value );
return null;
}
protected static byte[] encode( Byte value ) {
byte[] b = new byte[1];
b[0] = value.byteValue();
return b;
}
protected static byte[] encode( Boolean value ) {
byte[] b = new byte[1];
if ( value.booleanValue() )
b[0] = 1;
else
b[0] = 0;
return b;
}
protected static byte[] encode( int value ) {
return getBytes( value );
}
protected static byte[] encode( long value ) throws Exception {
return getBytes( value );
}
protected static byte[] encode( Date value ) {
return getBytes( value.getTime() );
}
protected static byte[] encode( Character value ) {
return encode( value.charValue() );
}
protected static byte[] encode( String value ) throws Exception {
return value.getBytes(CharEncoding.UTF_8);
}
protected static byte[] encode( StringBuffer value ) throws Exception {
return encode( value.toString() );
}
protected static byte[] encode( float value ) throws Exception {
return encode( (int)Float.floatToIntBits( value ) );
}
protected static byte[] encode( Short value ) throws Exception {
return encode( (int)value.shortValue() );
}
protected static byte[] encode( double value ) throws Exception {
return encode( (long)Double.doubleToLongBits( value ) );
}
protected static byte[] encode( StringBuilder value ) throws Exception {
return encode( value.toString() );
}
protected static byte[] encode( byte[] value ) {
return value;
}
protected static byte[] getBytes( long value ) {
byte[] b = new byte[8];
b[0] = (byte)((value >> 56) & 0xFF);
b[1] = (byte)((value >> 48) & 0xFF);
b[2] = (byte)((value >> 40) & 0xFF);
b[3] = (byte)((value >> 32) & 0xFF);
b[4] = (byte)((value >> 24) & 0xFF);
b[5] = (byte)((value >> 16) & 0xFF);
b[6] = (byte)((value >> 8) & 0xFF);
b[7] = (byte)((value >> 0) & 0xFF);
return b;
}
protected static byte[] getBytes( int value ) {
byte[] b = new byte[4];
b[0] = (byte)((value >> 24) & 0xFF);
b[1] = (byte)((value >> 16) & 0xFF);
b[2] = (byte)((value >> 8) & 0xFF);
b[3] = (byte)((value >> 0) & 0xFF);
return b;
}
/**
* Decodes byte array using memcache flag to determine type.
*
* @param b
* @param marker
* @return
* @throws Exception
*/
public static Object decode( byte[] b, int flag ) throws Exception {
if ( b.length < 1 )
return null;
if ( ( flag & MemcachedClient.MARKER_BYTE ) == MemcachedClient.MARKER_BYTE )
return decodeByte( b );
if ( ( flag & MemcachedClient.MARKER_BOOLEAN ) == MemcachedClient.MARKER_BOOLEAN )
return decodeBoolean( b );
if ( ( flag & MemcachedClient.MARKER_INTEGER ) == MemcachedClient.MARKER_INTEGER )
return decodeInteger( b );
if ( ( flag & MemcachedClient.MARKER_LONG ) == MemcachedClient.MARKER_LONG )
return decodeLong( b );
if ( ( flag & MemcachedClient.MARKER_CHARACTER ) == MemcachedClient.MARKER_CHARACTER )
return decodeCharacter( b );
if ( ( flag & MemcachedClient.MARKER_STRING ) == MemcachedClient.MARKER_STRING )
return decodeString( b );
if ( ( flag & MemcachedClient.MARKER_STRINGBUFFER ) == MemcachedClient.MARKER_STRINGBUFFER )
return decodeStringBuffer( b );
if ( ( flag & MemcachedClient.MARKER_FLOAT ) == MemcachedClient.MARKER_FLOAT )
return decodeFloat( b );
if ( ( flag & MemcachedClient.MARKER_SHORT ) == MemcachedClient.MARKER_SHORT )
return decodeShort( b );
if ( ( flag & MemcachedClient.MARKER_DOUBLE ) == MemcachedClient.MARKER_DOUBLE )
return decodeDouble( b );
if ( ( flag & MemcachedClient.MARKER_DATE ) == MemcachedClient.MARKER_DATE )
return decodeDate( b );
if ( ( flag & MemcachedClient.MARKER_STRINGBUILDER ) == MemcachedClient.MARKER_STRINGBUILDER )
return decodeStringBuilder( b );
if ( ( flag & MemcachedClient.MARKER_BYTEARR ) == MemcachedClient.MARKER_BYTEARR )
return decodeByteArr( b );
return null;
}
// decode methods
protected static Byte decodeByte( byte[] b ) {
return Byte.valueOf(b[0]);
}
protected static Boolean decodeBoolean( byte[] b ) {
boolean value = b[0] == 1;
return ( value ) ? Boolean.TRUE : Boolean.FALSE;
}
protected static Integer decodeInteger( byte[] b ) {
return Integer.valueOf(toInt(b));
}
protected static Long decodeLong( byte[] b ) throws Exception {
return Long.valueOf(toLong(b));
}
protected static Character decodeCharacter( byte[] b ) {
return Character.valueOf( (char)decodeInteger( b ).intValue() );
}
protected static String decodeString( byte[] b ) throws Exception {
return new String(b, CharEncoding.UTF_8);
}
protected static StringBuffer decodeStringBuffer( byte[] b ) throws Exception {
return new StringBuffer( decodeString( b ) );
}
protected static Float decodeFloat( byte[] b ) throws Exception {
Integer l = decodeInteger( b );
return Float.valueOf( Float.intBitsToFloat( l.intValue() ) );
}
protected static Short decodeShort( byte[] b ) throws Exception {
return Short.valueOf((short)decodeInteger(b).intValue());
}
protected static Double decodeDouble( byte[] b ) throws Exception {
Long l = decodeLong( b );
return Double.valueOf( Double.longBitsToDouble( l.longValue() ) );
}
protected static Date decodeDate( byte[] b ) {
return new Date( toLong( b ) );
}
protected static StringBuilder decodeStringBuilder( byte[] b ) throws Exception {
return new StringBuilder( decodeString( b ) );
}
protected static byte[] decodeByteArr( byte[] b ) {
return b;
}
/**
* This works by taking each of the bit patterns and converting them to
* ints taking into account 2s complement and then adding them..
*
* @param b
* @return
*/
protected static int toInt( byte[] b ) {
return (((((int) b[3]) & 0xFF) << 32) +
((((int) b[2]) & 0xFF) << 40) +
((((int) b[1]) & 0xFF) << 48) +
((((int) b[0]) & 0xFF) << 56));
}
protected static long toLong( byte[] b ) {
return ((((long) b[7]) & 0xFF) +
((((long) b[6]) & 0xFF) << 8) +
((((long) b[5]) & 0xFF) << 16) +
((((long) b[4]) & 0xFF) << 24) +
((((long) b[3]) & 0xFF) << 32) +
((((long) b[2]) & 0xFF) << 40) +
((((long) b[1]) & 0xFF) << 48) +
((((long) b[0]) & 0xFF) << 56));
}
}