// Commented for the Learning branch
package com.limegroup.bittorrent.bencoding;
import java.io.IOException;
import java.nio.channels.ReadableByteChannel;
import java.util.ArrayList;
import java.util.List;
/**
* A BEList object reads a bencoded list that starts "l" and ends "e" from a given channel and parses it into Java objects in an ArrayList.
* A bencoded list looks like this:
*
* l
* 5:hello
* i87e
* 23:here is a third element
* e
*
* Between "l" and "e" are any number of other bencoded elements.
* The list items can be strings, numbers, or even other lists.
*
* Here's how to use BEList to parse a bencoded list into Java objects.
* You've already read "l" from the channel, telling you it's a list that will come next.
* Make a new BEList object, giving it the channel it can read the rest of the bencoded data from.
* It will make a new empty ArrayList, this is the object that it will return when it's done.
* Call handleRead() on it every time there's more bencoded data for it to read in the channel.
*
* handleRead() makes a Token object called currentElement, it reads and parses the first list item.
* In the example above, currentElement will be a BEString that reads "5:hello" and turns it into a String.
* When currentElement is done, handleRead() gets the object it made and adds it to the result ArrayList.
* After all the elements, handleRead() will read the "e".
* When it happens, it sets done to true so isDone() starts returning true.
* Then, you can call getResult() to access the ArrayList with all the parsed objects.
*/
class BEList extends Token {
/** When this BEList object has read and parsed the whole list, it will set done to true. */
protected boolean done;
/** The current list element we're reading and parsing. */
protected Token currentElement;
/**
* Make a new BEList object that can read a bencoded list.
*
* @param chan The channel this new BEList can read bencoded data from
*/
BEList(ReadableByteChannel chan) {
// Save the channel in this object
super(chan);
// Make a new empty ArrayList, this is the object we'll fill and getResult() will return when we're done
result = createCollection();
}
/**
* Make a new empty ArrayList.
* Only the BEList constructor above calls this method.
*
* @return An ArrayList
*/
protected Object createCollection() {
// Make a new ArrayList and return it
return new ArrayList();
}
/**
* Add a given object we just parsed to the result ArrayList.
* Only handleRead() below calls this.
*
* @param o An object we just parsed
*/
protected void add(Object o) {
// Add the given object to our ArrayList
((List)result).add(o);
}
/**
* Read the next bencoded object from the channel, returning a type-specific object like a BEString that can read, parse, and make the object.
* Only handleRead() below calls this.
*
* @return An object that extends Token, like a BEString, that will parse the next bencoded piece of data in the list
*/
protected Token getNewElement() throws IOException {
// Give the channel to getNextToken(), which will read 1 character from it and return a type-specific parsing object
return getNextToken(chan);
}
/**
* The "NIODispatch" thread will call handleRead() when there's more bencoded in data for us to read and parse.
*
* A bencoded list looks like this:
*
* l 3:red 5:green 4:blue e
*
* We've already read the "l" for list from the channel, we did that to determine it's a list next and we need this BEList object to decode it.
* handleRead() calls getNewElement() to make a new object that extends Token that will parse the first element, "3:red".
* When that BEString is done, code here adds it to our result ArrayList.
* When we hit the "e", getNewElement() returns the Token.TERMINATOR object, and this method sets done to true.
*
* BEDictionary extends BEList, and doesn't have a handleRead() method.
* This means that when you call handleRead() on a BEDictionary, control will come here.
*/
public void handleRead() throws IOException {
// Code shouldn't call handleRead() after this BEList object has read the "e" and parsed the result
if (isDone()) throw new IllegalStateException("token is done, don't read to it");
// Loop until we read the "e" that ends the list, or we have a current parser returning because it needs more data
while (true) {
// If we don't have an object that extends Token reading the next bencoded object, make one
if (currentElement == null) currentElement = getNewElement(); // If this is a BEDictionary object, calls BEDictionary.getNewElement(), which returns a BEEntry object
// The channel couldn't even produce 1 character to tell us what kind of bencoded object is next
if (currentElement == null) return; // Read more later
// getNextToken() read "e", we've finished the list
if (currentElement.getResult() == Token.TERMINATOR) {
// We're done with the entire list
done = true;
return;
}
// Have the Token object we made read more bencoded data to get closer to finishing the list item it's parsing
currentElement.handleRead();
Object result = currentElement.getResult(); // getResult() returns null if it's not done yet
if (result == null) return; // Read more later
// currentElement parsed "e" (do)
if (result == Token.TERMINATOR) {
// We're done with the entire list
done = true;
return;
}
// Add the Java object we just parsed to our ArrayList of them
add(result);
currentElement = null; // Set currentElement to null so we'll make a new one next time
}
}
/**
* Find out if this BEList object is finished reading its bencoded list from its channel, and has parsed it into a ArrayList object of other objects.
*
* @return True, this object has read the entire bencoded list from the channel.
* False, call the handleRead() method more to get this object to read more bencoded data from its channel to reach the "e".
*/
protected boolean isDone() {
// Return true if handleRead() made a Token object that parsed the "e" that ends the list
return done;
}
/**
* Determine what kind of Token object this is, and what kind of Java object it will parse.
*
* @return Token.LIST, the code number for a BEList object that will produce a Java ArrayList
*/
public int getType() {
// Return the Token.LIST code
return LIST;
}
}