package net.spy.memcached.protocol.ascii;
import java.nio.ByteBuffer;
import java.util.Collection;
import net.spy.memcached.KeyUtil;
import net.spy.memcached.ops.GetOperation;
import net.spy.memcached.ops.GetsOperation;
import net.spy.memcached.ops.OperationCallback;
import net.spy.memcached.ops.OperationState;
import net.spy.memcached.ops.OperationStatus;
/**
* Base class for get and gets handlers.
*/
abstract class BaseGetOpImpl extends OperationImpl {
private static final OperationStatus END = new OperationStatus(true, "END");
private final String cmd;
private final Collection<String> keys;
private String currentKey = null;
private long casValue=0;
private int currentFlags = 0;
private byte[] data = null;
private int readOffset = 0;
private byte lookingFor = '\0';
public BaseGetOpImpl(String c,
OperationCallback cb, Collection<String> k) {
super(cb);
cmd=c;
keys=k;
}
/**
* Get the keys this GetOperation is looking for.
*/
public final Collection<String> getKeys() {
return keys;
}
@Override
public final void handleLine(String line) {
if(line.equals("END")) {
getLogger().debug("Get complete!");
getCallback().receivedStatus(END);
transitionState(OperationState.COMPLETE);
data=null;
} else if(line.startsWith("VALUE ")) {
getLogger().debug("Got line %s", line);
String[] stuff=line.split(" ");
assert stuff[0].equals("VALUE");
currentKey=stuff[1];
currentFlags=Integer.parseInt(stuff[2]);
data=new byte[Integer.parseInt(stuff[3])];
if(stuff.length > 4) {
casValue=Long.parseLong(stuff[4]);
}
readOffset=0;
getLogger().debug("Set read type to data");
setReadType(OperationReadType.DATA);
} else {
assert false : "Unknown line type: " + line;
}
}
@Override
public final void handleRead(ByteBuffer b) {
assert currentKey != null;
assert data != null;
// This will be the case, because we'll clear them when it's not.
assert readOffset <= data.length
: "readOffset is " + readOffset + " data.length is " + data.length;
getLogger().debug("readOffset: %d, length: %d",
readOffset, data.length);
// If we're not looking for termination, we're still looking for data
if(lookingFor == '\0') {
int toRead=data.length - readOffset;
int available=b.remaining();
toRead=Math.min(toRead, available);
getLogger().debug("Reading %d bytes", toRead);
b.get(data, readOffset, toRead);
readOffset+=toRead;
}
// Transition us into a ``looking for \r\n'' kind of state if we've
// read enough and are still in a data state.
if(readOffset == data.length && lookingFor == '\0') {
// The callback is most likely a get callback. If it's not, then
// it's a gets callback.
try {
GetOperation.Callback gcb=(GetOperation.Callback)getCallback();
gcb.gotData(currentKey, currentFlags, data);
} catch(ClassCastException e) {
GetsOperation.Callback gcb=(GetsOperation.Callback)
getCallback();
gcb.gotData(currentKey, currentFlags, casValue, data);
}
lookingFor='\r';
}
// If we're looking for an ending byte, let's go find it.
if(lookingFor != '\0' && b.hasRemaining()) {
do {
byte tmp=b.get();
assert tmp == lookingFor : "Expecting " + lookingFor + ", got "
+ (char)tmp;
switch(lookingFor) {
case '\r': lookingFor='\n'; break;
case '\n': lookingFor='\0'; break;
default:
assert false: "Looking for unexpected char: "
+ (char)lookingFor;
}
} while(lookingFor != '\0' && b.hasRemaining());
// Completed the read, reset stuff.
if(lookingFor == '\0') {
currentKey=null;
data=null;
readOffset=0;
currentFlags=0;
getLogger().debug("Setting read type back to line.");
setReadType(OperationReadType.LINE);
}
}
}
@Override
public final void initialize() {
// Figure out the length of the request
int size=6; // Enough for gets\r\n
Collection<byte[]> keyBytes=KeyUtil.getKeyBytes(keys);
for(byte[] k : keyBytes) {
size+=k.length;
size++;
}
ByteBuffer b=ByteBuffer.allocate(size);
b.put(cmd.getBytes());
for(byte[] k : keyBytes) {
b.put((byte)' ');
b.put(k);
}
b.put("\r\n".getBytes());
b.flip();
setBuffer(b);
}
@Override
protected final void wasCancelled() {
getCallback().receivedStatus(CANCELLED);
}
}