// Commented for the Learning branch
package com.limegroup.bittorrent.bencoding;
import java.io.IOException;
import java.nio.channels.ReadableByteChannel;
import java.util.HashMap;
import java.util.Map;
/**
* A BEDictionary object reads a bencoded dictionary that starts "d" and ends "e" from a given channel and parses it into Java objects in a HashMap.
* A bencoded dictionary looks like this:
*
* d
* 5:color 5:green
* 4:year i2006e
* 9:platforms l 3:mac 7:windows 5:linux e
* 6:flavor 4:lime
* e
*
* The dictionary entries are pairs of keys and values.
* The keys are all bencoded strings.
* The values can be any bencoded object.
* Above, the keys "color" and "flavor" map to string values, while "year" maps to a number.
* The key "platforms" maps to a list.
* A key can even map to another dictionary.
*
* BEDictionary produces a Java HashMap of keys and values.
* The keys are Java String objects that hold the name of each key.
* The values are Java objects specific to the type of object stored under the key in the dictionary.
*
* BEDictionary extends BEList.
* BEDictionary doesn't have a handleRead() method.
* When you call handleRead() on a BEDictionary, the call goes to BEList.handleRead().
* When that handleRead() method calls getNewElement(), control returns here and handleRead() gets a new BEEntry object.
*/
class BEDictionary extends BEList {
/**
* Make a new BEDictionary object that can read and parse a bencoded dictionary.
*
* @param chan The channel this new object can read bencoded data from
*/
BEDictionary(ReadableByteChannel chan) {
// Save the channel in this object
super(chan);
}
/**
* Determine what kind of Token object this is, and what kind of Java object it will parse.
*
* @return Token.DICTIONARY, the code number for a BEDictionary object that will produce a Java HashMap
*/
public int getType() {
// Return the Token.DICTIONARY code
return DICTIONARY;
}
/**
* Make the object this BEDictionary will fill with BEEntry objects that hold keys and values, and return.
*
* @return A new empty HashMap
*/
protected Object createCollection() {
// Make and return a new empty HashMap for this BEDictionary to parse its values into
return new HashMap();
}
/**
* Add a key and value we parsed to the result HashMap this BEDictionary is preparing.
*
* @param o A BEEntry object with a string key and object value
*/
protected void add(Object o) {
// Add the key and value of the given BEEntry to the result HashMap
Map m = (Map)result; // result is a HashMap object that createCollection() made
BEEntry e = (BEEntry)o; // The given Object is actually a BEEntry
m.put(e.key, e.value); // Add the key and value from the BEEntry to the HashMap
}
/**
* Make a new BEEntry object that will read and parse a key and value pair of bencoded data.
* Control comes here when BEList.handleRead() calls getNewElement() and this object is actually a BEDictionary.
*
* @return A new BEEntry object that will read and parse a key and value pair of bencoded data
*/
protected Token getNewElement() {
// Make a new BEEntry object that will read and parse a key and value pair of bencoded data
return new BEEntry(chan);
}
/**
* A BEEntry object can read and parse a single entry in a bencoded dictionary.
* For instance, a dictionary might look like this:
*
* d
* 5:color 5:green
* 6:flavor 4:lime
* e
*
* Make a BEEntry object to parse one entry, like "5:color5:green".
* When it's done, the String BEEntry.key will be "color", and the Object BEEntry.value will be the String "green".
*/
private static class BEEntry extends Token {
/** The BEString object that will parse the key name, like "5:color". */
private BEString keyToken;
/** The String key, like "color". */
private String key;
/** The object that extends Token that will parse the value object, like a BEString to parse "5:green". */
private Token valueToken;
/** The value object, like the String "green". */
private Object value;
/** True if this is the last entry in the dictionary. */
private boolean lastEntry;
/**
* Make a new BEEntry object that will parse a single key and value in the middle of a bencoded dictionary.
*
* @param chan The channel this object can read bencoded data from
*/
BEEntry (ReadableByteChannel chan) {
// Save the given channel in this object
super(chan);
// When this BEEntry object is done reading one key and value, the Java object it made is itself
result = this;
}
/**
* The "NIODispatch" thread will call this handleRead() method when this BEEntry object can read more bencoded data from its channel.
*
* A key and value pair in a bencoded dictionary looks like "5:color5:green".
* keyToken is a BEString object that will read "5:color" into the String key "color".
* valueToken is an object that extends token that will read "5:green" into the Object value "green".
* This handleRead() method tries to read the key and value.
* You can tell how far it got by whether or not key and value are null when it returns.
*/
public void handleRead() throws IOException {
// If we haven't read the key, like "5:color", yet
if (keyToken == null && // We don't have a BEString object to parse the text key yet, and
key == null) { // We don't have the parsed String key, like "color" yet either
// Read the next character from the channel
Token t = getNextToken(chan);
// getNextToken read a character from the channel, and made an object that extends Token that can read and parse it
if (t != null) {
// The character was a numeral "0" through "9", a string key like "5:color" is in the channel next
if (t instanceof BEString) {
// Save the BEString object as this BEEntry's keyToken, and keep going in this method
keyToken = (BEString)t;
// The character was "e", marking the end of the whole dictionary
} else if (t == Token.TERMINATOR) {
// Set the last entry flag, and leave now
lastEntry = true;
return;
// The character was something else
} else {
// Bencoded dictionary entries have to start with a bencoded string key, or "e" for the end
throw new IOException("invalid entry - key not a string");
}
// The channel couldn't even give us 1 character
} else {
// Try again next time
return;
}
}
/*
* If control reaches here, we have a BEString object named keyToken parsing the key.
*/
// We have a BEString named keyToken parsing the key like "5:color", but it hasn't finished yet
if (key == null) {
// Tell it to read more bencoded data from its channel
keyToken.handleRead();
// It's done parsing the String "color"
if (keyToken.getResult() != null) {
// Save the String key
key = new String((byte[])keyToken.getResult(), Token.ASCII);
// We don't need the BEString object that parsed it for us any longer
keyToken = null;
// It hasn't read the whole string yet
} else {
// Try to read more next time
return;
}
}
/*
* If control reaches here, we have read the String key in the dictionary entry this BEEntry object is parsing.
*/
// We haven't read the value yet
if (valueToken == null && value == null) {
// Read the next character of bencoded data, and get an object that extends Token that will read and parse it into a Java object
Token t = getNextToken(chan);
if (t != null) valueToken = t; // Save it
else return; // The channel couldn't give us one character, try to figure out the value's type next time the program calls handleRead()
}
// We've created the valueToken that will parse the value, but it hasn't parsed the whole value yet
if (value == null) {
// Tell valueToken to read more bencoded data, and get the object it will make
valueToken.handleRead();
value = valueToken.getResult();
// It read "e" for end, which doesn't make sense, because there should be a value for each key entry
if (value == Token.TERMINATOR) throw new IOException("missing value");
// Clear our reference to the Java object valueToken read
if (value != null) valueToken = null;
// We've parsed and read the whole value
} else {
// Code shouldn't call handleRead() after we're done parsing the value
throw new IllegalStateException("token is done - don't read to it " + key + " " + value);
}
}
/**
* Determine if this BEEntry object is finished reading a key and value from a bencoded dictionary.
*
* @return True if key and value are objects we parsed and made.
* False if one or both of them are still null, and you need to call handleRead() some more.
*/
protected boolean isDone() {
// Return true if handleRead() has made the key and value objects
return key != null && value != null;
}
/**
* Get this BEEntry object, which holds the key and value it read and parsed.
*
* @return A reference to this BEEntry object
*/
public Object getResult() {
// If this BEEntry object read the terminating "e", return the TERMINATOR object
if (lastEntry) return Token.TERMINATOR;
// Call Token.getResult(), which returns result, which the BEEntry constructor set to this
return super.getResult();
}
}
}