com.meetup.memcached.NativeHandler.java Source code

Java tutorial

Introduction

Here is the source code for com.meetup.memcached.NativeHandler.java

Source

/**
 * 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));
    }
}