package org.limewire.bittorrent.bencoding; import java.io.IOException; import java.nio.channels.ReadableByteChannel; import java.util.HashMap; import java.util.Map; /** * A token used to represent a bencoded Dictionary (Mapping) of keys to values. */ class BEDictionary extends BEAbstractCollection<Map<String, Object>> { private final String charsetName; public BEDictionary(ReadableByteChannel channel, String charsetName) { super(channel); this.charsetName = charsetName; } BEDictionary(ReadableByteChannel chan) { this(chan, Token.ASCII); } @Override public int getType() { return DICTIONARY; } @Override protected Map<String, Object> createCollection() { return new HashMap<String, Object>(); } @Override protected void add(Object o) { BEEntry e = (BEEntry)o; result.put(e.key, e.value); } @Override protected Token<?> getNewElement() { return new BEEntry(chan, charsetName); } /** * A token used to represent a bencoded mapping of a Key -> Value. * The Key must be a String. */ private static class BEEntry extends Token<Object> { /** Token for the parsing of the key */ private BEString keyToken; /** The key itself */ private String key; /** Token for the parsing of the value */ private Token valueToken; /** The value itself */ private Object value; /** Whether this is the last entry in the map */ private boolean lastEntry; private final String charsetName; BEEntry (ReadableByteChannel chan, String charsetName) { super(chan); this.charsetName = charsetName; result = this; } @Override public void handleRead() throws IOException { if (keyToken == null && key == null) { Token t = getNextToken(chan); if (t != null) { if (t instanceof BEString) { keyToken = (BEString)t; } else if (t == Token.TERMINATOR) { lastEntry = true; return; } else throw new IOException("invalid entry - key not a string"); } else return; // try again next time } if (key == null) { keyToken.handleRead(); if (keyToken.getResult() != null) { // technically keys don't necessarily need to be String objects // but in practice they are key = new String(keyToken.getResult(), charsetName); keyToken = null; } else return; // try again next time } // if we got here we have fully read the key if (valueToken == null && value == null) { Token t = getNextToken(chan); if (t != null) valueToken = t; else return; // try to figure out which type of token the value is next time } // we've read the type of the value, but not the value itself if (value == null) { valueToken.handleRead(); value = valueToken.getResult(); if (value == Token.TERMINATOR) throw new IOException("missing value"); if (value != null) valueToken = null; //clean the ref } else throw new IllegalStateException("token is done - don't read to it "+key+" "+value); } @Override protected boolean isDone() { return key != null && value != null; } @Override public Object getResult() { if (lastEntry) return Token.TERMINATOR; return super.getResult(); } @Override public int getType() { return INTERNAL; } } }