package org.greencheek.elasticacheconfig.decoder;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ReplayingDecoder;
import org.greencheek.elasticacheconfig.domain.ClusterConfigurationBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.nio.charset.Charset;
import java.util.List;
public class ConfigInfoDecoder extends ReplayingDecoder<ConfigInfoDecodingState> {
private static final RuntimeException INVALID_VERSION = new InvalidConfigVersionException();
private static final Logger log = LoggerFactory.getLogger(ConfigInfoDecoder.class);
private final ClusterConfigurationBuilder configBuilder = new ClusterConfigurationBuilder();
public ConfigInfoDecoder() {
// Set the initial state.
super(ConfigInfoDecodingState.HEADER);
}
private boolean readStringLine(ByteBuf byteBuf, ConfigInfoDecodingState currentState, ConfigInfoDecodingState stateToTransitionTo) {
int eol = findEndOfLine(byteBuf);
if(eol!=-1) {
String line = readLine(eol, byteBuf).toString(Charset.forName("UTF-8"));
log.debug("Config line read: {}",line);
configBuilder.setValue(line, currentState);
checkpoint(stateToTransitionTo);
return true;
} else {
return false;
}
}
private boolean readLongLine(final ChannelHandlerContext ctx,ByteBuf byteBuf, ConfigInfoDecodingState currentState,ConfigInfoDecodingState stateToTransitionTo) {
int eol = findEndOfLine(byteBuf);
if(eol!=-1) {
String version = readLine(eol,byteBuf).toString(Charset.forName("UTF-8"));
log.debug("Config line read: {}",version);
long versionNumber = parseLong(version, Long.MIN_VALUE);
if(versionNumber==Long.MIN_VALUE) {
ctx.fireExceptionCaught(INVALID_VERSION);
}
configBuilder.setValue(versionNumber, currentState);
checkpoint(stateToTransitionTo);
return true;
}
else {
return false;
}
}
public static long parseLong(String s,long invalidValue)
throws NumberFormatException
{
int radix = 10;
if (s == null) {
return invalidValue;
}
if (radix < Character.MIN_RADIX) {
return invalidValue;
}
if (radix > Character.MAX_RADIX) {
return invalidValue;
}
long result = 0;
boolean negative = false;
int i = 0, len = s.length();
long limit = -Long.MAX_VALUE;
long multmin;
int digit;
if (len > 0) {
char firstChar = s.charAt(0);
if (firstChar < '0') { // Possible leading "+" or "-"
if (firstChar == '-') {
negative = true;
limit = Long.MIN_VALUE;
} else if (firstChar != '+') {
return invalidValue;
}
if (len == 1) // Cannot have lone "+" or "-"
{
return invalidValue;
}
i++;
}
multmin = limit / radix;
while (i < len) {
// Accumulating negatively avoids surprises near MAX_VALUE
digit = Character.digit(s.charAt(i++),radix);
if (digit < 0) {
return invalidValue;
}
if (result < multmin) {
return invalidValue;
}
result *= radix;
if (result < limit + digit) {
return invalidValue;
}
result -= digit;
}
} else {
return invalidValue;
}
return negative ? result : -result;
}
protected void reset() {
configBuilder.init();
this.state(ConfigInfoDecodingState.HEADER);
}
@Override
protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> objects) throws Exception {
switch (state()) {
case HEADER :
if(!readStringLine(byteBuf, ConfigInfoDecodingState.HEADER, ConfigInfoDecodingState.VERSION)) {
byteBuf.readBytes(Integer.MAX_VALUE);
}
case VERSION:
if(!readLongLine(channelHandlerContext,byteBuf, ConfigInfoDecodingState.VERSION, ConfigInfoDecodingState.NODES)) {
byteBuf.readBytes(Integer.MAX_VALUE);
}
case NODES:
if(!readStringLine(byteBuf, ConfigInfoDecodingState.NODES, ConfigInfoDecodingState.BLANK)) {
byteBuf.readBytes(Integer.MAX_VALUE);
}
case BLANK:
if(!readStringLine(byteBuf, ConfigInfoDecodingState.BLANK, ConfigInfoDecodingState.END)) {
byteBuf.readBytes(Integer.MAX_VALUE);
}
case END:
if(!readStringLine(byteBuf, ConfigInfoDecodingState.END, ConfigInfoDecodingState.HEADER)) {
byteBuf.readBytes(Integer.MAX_VALUE);
}
objects.add(configBuilder.build());
reset();
break;
default:
throw new Error("Unknown decoding state: ");
}
}
private ByteBuf readLine(int eol,ByteBuf buffer) {
final ByteBuf frame;
final int length = eol - buffer.readerIndex();
final int delimLength = buffer.getByte(eol) == '\r'? 2 : 1;
frame = buffer.readSlice(length);
buffer.skipBytes(delimLength);
return frame.retain();
}
/**
* Returns the index in the buffer of the end of line found.
* Returns -1 if no end of line was found in the buffer.
*/
private static int findEndOfLine(final ByteBuf buffer) {
final int n = buffer.writerIndex();
for (int i = buffer.readerIndex(); i < n; i ++) {
final byte b = buffer.getByte(i);
if (b == '\n') {
return i;
} else if (b == '\r' && i < n - 1 && buffer.getByte(i + 1) == '\n') {
return i; // \r\n
}
}
return -1; // Not found.
}
}