// Commented for the Learning branch
package com.limegroup.bittorrent.bencoding;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
/**
* The BEncoder class has code that can convert Java objects into bencoded data for BitTorrent.
* Call BEncoder.encode(OutputStream, Object) to bencode a given Object and write the bencoded data to the given OutputStream.
*
* BitTorrent uses a simple and extensible data format called bencoding.
* More information on bencoding is on the Web at:
* http://en.wikipedia.org/wiki/Bencoding
* http://www.bittorrent.org/protocol.html in the section titled "The connectivity is as follows".
*
* Bencoded data is composed of strings, numbers, lists, and dictionaries.
* Strings are prefixed by their length, like "5:hello".
* Numbers are written as text numerals between the letters "i" and "e", like "i87e".
* You can list any number of bencoded pieces of data between "l" for list and "e" for end.
* A dictionary is a list of key and value pairs between "d" and "e".
* The keys have to be strings, and they have to be in alphabetical order.
*/
public class BEncoder {
/**
* The BEncoder() constructor is marked private to prevent anyone from making a BEncoder object.
* Just use the public static methods like encodeString() instead.
*/
private BEncoder() {}
/**
* Bencode the given byte array to the given OutputStream.
*
* Writes the length, a colon, and then the text.
* For example, the string "hello" becomes the bencoded bytes "5:hello".
*
* @param output An OutputStream for this method to write bencoded data to
* @param b The byte array to bencode and write
*/
public static void encodeByteArray(OutputStream output, byte[] b) throws IOException {
// Write the length, a colon, and the byte array, like "5:hello"
String length = String.valueOf(b.length); // Find out how long the data is, and convert that number into a String
output.write(length.getBytes(Token.ASCII)); // Write the text to the given OutputStream
output.write(BEString.COLON); // Write the ":"
output.write(b); // Write the data
}
/**
* Bencode the given number to the given OutputStream.
*
* Writes the base-10 numerals of the number between the letters "i" and "e".
* For example, the if n is 87 encodeInt() will write "i87e" to the OutputStream.
*
* @param output An OutputStream for this method to write bencoded data to
* @param n The number to bencode and write
*/
public static void encodeInt(OutputStream output, Number n) throws IOException {
// Write the number between an "i" and "e", like "i87e"
String numerals = String.valueOf(n.longValue()); // Convert the number into a String
output.write(Token.I); // Write the "i"
output.write(numerals.getBytes(Token.ASCII)); // Write the numerals
output.write(Token.E); // Write the "e"
}
/**
* Bencodes the given List to the given OutputStream.
* Pass any Java object that extends java.util.List for the list parameter.
*
* Writes "l" for list, the bencoded-form of each of the given objects, and then "e" for end.
*
* @param output An OutputStream for this method to write bencoded data to
* @param list A Java List object to bencode and write
*/
public static void encodeList(OutputStream output, List list) throws IOException {
// Write "l", the bencoded form of each element in the List, and "e"
output.write(Token.L);
for (Iterator iter = list.iterator(); iter.hasNext(); ) encode(output, iter.next()); // Loop for each object, calling encode() on each one
output.write(Token.E);
}
/**
* Bencodes the given Map to the given OutputStream.
* Pass any Java object that extends java.util.Map for the map parameter.
*
* Writes a bencoded dictionary, which is a list of keys and values which looks like this:
*
* d
* 5:color 5:green
* 6:flavor 4:lime
* 5:shape 5:round
* e
*
* The bencoded data starts "d" for dictionary and ends "e" for end.
* In the middle are pairs of bencoded values.
* The keys have to be strings, while the values can be strings, numbers, lists, or more dictionaries.
* The keys have to be in alphabetical order.
*
* @param o An OutputStream for this method to write bencoded data to
* @param map The Java Map object to bencode and write
*/
public static void encodeDict(OutputStream output, Map map) throws IOException {
// The BitTorrent specification requires that dictionary keys are sorted in alphanumeric order
SortedMap sorted = new TreeMap(); // A Java TreeMap will automatically sort the items we add to it
for (Iterator iter = map.keySet().iterator(); iter.hasNext(); ) { // Loop for each key and value pair in the given Map
Object key = iter.next();
sorted.put(key.toString(), map.get(key)); // Copy it into the TreeMap, which will put it in sorted order
}
// Write "d", each bencoded key and value, and then "e"
output.write(Token.D);
for (Iterator iter = sorted.keySet().iterator(); iter.hasNext(); ) {
String key = (String)iter.next();
// Write the key, a bencoded string like "4:key1"
encodeByteArray(output, key.getBytes(Token.ASCII));
// Write the value, which can be any kind of bencoded data
encode(output, sorted.get(key));
}
output.write(Token.E);
}
/**
* Describes a given object using bencoding, and writes the bencoded data to the given stream.
*
* @param output An OutputStream for this method to write bencoded data to.
* @param object The Java Object to bencode and write.
* To write a bencoded dictionary, pass a Map object.
* To write a bencoded list, pass a List object.
* To write a bencoded number, pass a Number object.
* To write a bencoded string, pass a String or just a byte array.
* @throws IOException If there was a problem reading from the OutputStream.
* IllegalArgumentException If you pass an object that isn't a Map, List, Number, String, or byte array.
*/
private static void encode(OutputStream output, Object object) throws IOException {
// Sort the object by its type, having the matching method bencode it and write it
if (object instanceof Map) encodeDict(output, (Map)object);
else if (object instanceof List) encodeList(output, (List)object);
else if (object instanceof Number) encodeInt(output, (Number)object);
else if (object instanceof String) encodeByteArray(output, ((String)object).getBytes(Token.ASCII));
else if (object instanceof byte[]) encodeByteArray(output, (byte[])object);
else throw new IllegalArgumentException();
}
}