package the8472.bencode;
import static the8472.bencode.Utils.buf2ary;
import static the8472.bencode.Utils.buf2str;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import the8472.bencode.Tokenizer.BDecodingException;
import the8472.bencode.Tokenizer.Token;
import the8472.bencode.Tokenizer.TokenConsumer;
public class BDecoder {
private class Consumer implements TokenConsumer {
Object[] stack = new Object[256];
String keyPendingInsert;
int depth = 0;
@Override
public void push(Token st) {
//System.out.println("push"+st.type());
switch(st.type()) {
case DICT:
Object o = new HashMap<String, Object>();
putObject(o);
pushInternal(o);
break;
case LIST:
o = new ArrayList<>();
putObject(o);
pushInternal(o);
break;
case LONG:
case STRING:
case PREFIXED_STRING:
return;
default:
throw new IllegalStateException("this shouldn't be happening");
}
depth++;
}
void pushInternal(Object o) {
stack[depth] = o;
}
void putObject(Object o) {
if(depth == 0) {
stack[0] = o;
return;
}
Object container = stack[depth - 1];
if(container.getClass() == HashMap.class) {
if(keyPendingInsert != null) {
if(o instanceof ByteBuffer)
o = buf2ary((ByteBuffer)o);
if(((HashMap<String, Object>)container).put(keyPendingInsert, o) != null)
throw new BDecodingException("duplicate key found in dictionary");
keyPendingInsert = null;
} else {
keyPendingInsert = buf2str((ByteBuffer)o);
}
} else if(container.getClass() == ArrayList.class) {
if(o instanceof ByteBuffer)
o = buf2ary((ByteBuffer)o);
((ArrayList<Object>)container).add(o);
} else {
throw new RuntimeException("this should not happen");
}
}
@Override
public void pop(Token st) {
//System.out.println("pop"+st.type());
switch(st.type()) {
case DICT:
case LIST:
depth--;
return;
case LONG:
putObject(t.lastDecodedNum);
break;
case STRING:
putObject(t.getSlice(st));
break;
case PREFIXED_STRING:
return;
default:
throw new IllegalStateException("this shouldn't be happening");
}
}
void reset() {
Arrays.fill(stack, null);
keyPendingInsert = null;
depth = 0;
}
}
public Map<String, Object> decode(ByteBuffer buf) {
Object root = decodeInternal(buf);
if(root instanceof Map) {
return (Map<String, Object>) root;
}
throw new BDecodingException("expected dictionary as root object");
}
public Object decodeAny(ByteBuffer buf) {
return decodeInternal(buf);
}
final Tokenizer t;
final Consumer c;
public BDecoder() {
t = new Tokenizer();
c = new Consumer();
t.consumer(c);
}
private Object decodeInternal(ByteBuffer buf) {
try {
t.inputBuffer(buf);
t.tokenize();
return c.stack[0];
} finally {
// promptly release references and clean up any possibly illegal states to allow safe reuse
t.reset();
c.reset();
}
}
}