package com.rubiconproject.oss.kv.transcoder; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.Date; import java.util.zip.GZIPInputStream; import java.util.zip.GZIPOutputStream; import com.rubiconproject.oss.kv.util.StreamUtils; /** * A smart generic transcoder. Copied from Tokyo Tyrant: * * http://bitbucket.org/EP/tokyotyrant-java/src/tip/src/main/java/tokyotyrant/transcoder/SerializingTranscoder.java * * @author samtingleff * */ public class SerializingTranscoder implements Transcoder { static final byte TYPE_STRING = 0; static final byte TYPE_BOOLEAN = 1; static final byte TYPE_INTEGER = 2; static final byte TYPE_LONG = 3; static final byte TYPE_DATE = 4; static final byte TYPE_BYTE = 5; static final byte TYPE_FLOAT = 6; static final byte TYPE_DOUBLE = 7; static final byte TYPE_BYTEARRAY = 8; static final byte TYPE_SERIALIZABLE = Byte.MAX_VALUE; static final byte COMPRESSED = (byte) 0x80; /** * Use network byte order. */ private final ByteOrder byteOrder = ByteOrder.BIG_ENDIAN; private final StringTranscoder stringTranscoder = new StringTranscoder(); private final ByteTranscoder byteTranscoder = new ByteTranscoder(); private final IntegerTranscoder integerTranscoder = new IntegerTranscoder( byteOrder); private final LongTranscoder longTranscoder = new LongTranscoder(byteOrder); private final FloatTranscoder floatTranscoder = new FloatTranscoder( byteOrder); private final DoubleTranscoder doubleTranscoder = new DoubleTranscoder( byteOrder); private final ByteArrayTranscoder byteArrayTranscoder = new ByteArrayTranscoder(); private final SerializableTranscoder serializableTranscoder = new SerializableTranscoder(); private final int compressionThreshold; public SerializingTranscoder() { this(16 * 1024); } public SerializingTranscoder(int compressionThreshold) { this.compressionThreshold = compressionThreshold; } public byte[] encode(Object decoded) throws IOException { byte flag; byte[] body; if (decoded instanceof String) { flag = TYPE_STRING; body = stringTranscoder.encode(decoded); } else if (decoded instanceof Boolean) { flag = TYPE_BOOLEAN; body = encodeBoolean((Boolean) decoded); } else if (decoded instanceof Integer) { flag = TYPE_INTEGER; body = integerTranscoder.encode(decoded); } else if (decoded instanceof Long) { flag = TYPE_LONG; body = longTranscoder.encode(decoded); } else if (decoded instanceof Date) { flag = TYPE_DATE; body = encodeDate((Date) decoded); } else if (decoded instanceof Byte) { flag = TYPE_BYTE; body = byteTranscoder.encode(decoded); } else if (decoded instanceof Float) { flag = TYPE_FLOAT; body = floatTranscoder.encode(decoded); } else if (decoded instanceof Double) { flag = TYPE_DOUBLE; body = doubleTranscoder.encode(decoded); } else if (decoded instanceof byte[]) { flag = TYPE_BYTEARRAY; body = byteArrayTranscoder.encode(decoded); } else { flag = TYPE_SERIALIZABLE; body = serializableTranscoder.encode(decoded); } if (compressionThreshold > 0 && body.length > compressionThreshold) { byte[] compressed = compress(body); if (compressed.length < body.length) { flag |= COMPRESSED; body = compressed; } } ByteBuffer buf = ByteBuffer.allocate(1 + body.length); buf.put(flag); buf.put(body); return buf.array(); } public Object decode(byte[] encoded) throws IOException { Object decoded; ByteBuffer buf = ByteBuffer.wrap(encoded); byte flag = buf.get(); byte[] body = new byte[buf.remaining()]; System.arraycopy(buf.array(), 1, body, 0, body.length); if ((flag & COMPRESSED) != 0) { body = decompress(body); } switch (flag & ~COMPRESSED) { case TYPE_STRING: decoded = stringTranscoder.decode(body); break; case TYPE_BOOLEAN: decoded = decodeBoolean(body); break; case TYPE_INTEGER: decoded = integerTranscoder.decode(body); break; case TYPE_LONG: decoded = longTranscoder.decode(body); break; case TYPE_DATE: decoded = decodeDate(body); break; case TYPE_BYTE: decoded = byteTranscoder.decode(body); break; case TYPE_FLOAT: decoded = floatTranscoder.decode(body); break; case TYPE_DOUBLE: decoded = doubleTranscoder.decode(body); break; case TYPE_BYTEARRAY: decoded = byteArrayTranscoder.decode(body); break; default: decoded = serializableTranscoder.decode(body); } return decoded; } protected byte[] encodeBoolean(boolean value) { return byteTranscoder.encode(((byte) (value ? 1 : 0))); } protected Boolean decodeBoolean(byte[] body) { return (Byte) byteTranscoder.decode(body) == 0 ? false : true; } protected byte[] encodeDate(Date value) { return longTranscoder.encode((value).getTime()); } protected Date decodeDate(byte[] body) { return new Date((Long) longTranscoder.decode(body)); } protected byte[] compress(byte[] data) { ByteArrayOutputStream buffer = null; GZIPOutputStream gzip = null; try { buffer = new ByteArrayOutputStream(); gzip = new GZIPOutputStream(buffer); gzip.write(data); } catch (IOException e) { throw new RuntimeException("Unable to compress data", e); } finally { closeQuietly(gzip); closeQuietly(buffer); } return buffer.toByteArray(); } protected byte[] decompress(byte[] data) { ByteArrayOutputStream buffer = null; GZIPInputStream gzip = null; try { buffer = new ByteArrayOutputStream(); gzip = new GZIPInputStream(new ByteArrayInputStream(data)); copy(gzip, buffer); } catch (IOException e) { throw new RuntimeException("Unable to decompress data", e); } finally { closeQuietly(gzip); closeQuietly(buffer); } return buffer.toByteArray(); } private static void copy(InputStream input, OutputStream output) throws IOException { StreamUtils.copyStreamToStream(input, output); } private static void closeQuietly(InputStream input) { try { if (input != null) { input.close(); } } catch (IOException ioe) { } } private static void closeQuietly(OutputStream output) { try { if (output != null) { output.close(); } } catch (IOException ioe) { } } }