package redis.netty;
import com.google.common.base.Charsets;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.buffer.ChannelBufferIndexFinder;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.handler.codec.replay.ReplayingDecoder;
import org.jboss.netty.handler.codec.replay.VoidEnum;
import java.io.IOException;
/**
* Netty codec for Redis
*/
public class RedisDecoder extends ReplayingDecoder<VoidEnum> {
public static final char CR = '\r';
public static final char LF = '\n';
private static final char ZERO = '0';
// We track the current multibulk reply in the case
// where we do not get a complete reply in a single
// decode invocation.
private MultiBulkReply reply;
public ChannelBuffer readBytes(ChannelBuffer is) throws IOException {
long size = readLong(is);
if (size == -1) {
return null;
}
if (size > Integer.MAX_VALUE) {
throw new IllegalArgumentException("Java only supports arrays up to " + Integer.MAX_VALUE + " in size");
}
ChannelBuffer buffer = is.readSlice((int) size);
int cr = is.readByte();
int lf = is.readByte();
if (cr != CR || lf != LF) {
throw new IOException("Improper line ending: " + cr + ", " + lf);
}
return buffer;
}
public static long readLong(ChannelBuffer is) throws IOException {
long size = 0;
int sign = 1;
int read = is.readByte();
if (read == '-') {
read = is.readByte();
sign = -1;
}
do {
if (read == CR) {
if (is.readByte() == LF) {
break;
}
}
int value = read - ZERO;
if (value >= 0 && value < 10) {
size *= 10;
size += value;
} else {
throw new IOException("Invalid character in integer");
}
read = is.readByte();
} while (true);
return size * sign;
}
public Reply receive(final ChannelBuffer is) throws IOException {
// We may be in the middle of a large multibulk reply
if (reply != null) {
return decodeMultiBulkReply(is);
}
return readReply(is);
}
public Reply readReply(ChannelBuffer is) throws IOException {
int code = is.readByte();
switch (code) {
case StatusReply.MARKER: {
String status = is.readBytes(is.bytesBefore(ChannelBufferIndexFinder.CRLF)).toString(Charsets.UTF_8);
is.skipBytes(2);
return new StatusReply(status);
}
case ErrorReply.MARKER: {
String error = is.readBytes(is.bytesBefore(ChannelBufferIndexFinder.CRLF)).toString(Charsets.UTF_8);
is.skipBytes(2);
return new ErrorReply(error);
}
case IntegerReply.MARKER: {
return new IntegerReply(readLong(is));
}
case BulkReply.MARKER: {
return new BulkReply(readBytes(is));
}
case MultiBulkReply.MARKER: {
if (reply == null) {
return decodeMultiBulkReply(is);
} else {
// This is an internal MBR in an MBR.
return new RedisDecoder().decodeMultiBulkReply(is);
}
}
default: {
throw new IOException("Unexpected character in stream: " + code);
}
}
}
@Override
public void checkpoint() {
super.checkpoint();
}
@Override
protected Object decode(ChannelHandlerContext channelHandlerContext, Channel channel, ChannelBuffer channelBuffer, VoidEnum anEnum) throws Exception {
return receive(channelBuffer);
}
public MultiBulkReply decodeMultiBulkReply(ChannelBuffer is) throws IOException {
try {
if (reply == null) {
reply = new MultiBulkReply();
checkpoint();
}
reply.read(this, is);
return reply;
} finally {
// If we throw a replay exception we won't be
// done yet and will need to contiue parsing.
if (reply != null && reply.isDone()) {
reply = null;
}
}
}
}