Saturday, February 19, 2011

Parsing Bencode format

package au.modi.downloader.core;

import java.io.File;
import java.io.IOException;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Stack;

/**
* @author Kartik Modi
* @email eng.kartik@gmail.com
* Parse Bencoded file.
* Map and List is used to display values.
*/
public class ParsingLogic
{
/** Current position of the cursor. */
private int currentPosition = 0;

/** The parsing contents. */
private String contents;

/**
* The contents of the file as a string.
* @param contents The string to parse.
*/
public ParsingLogic(String contents)
{
this.contents = contents;
}

/**
* Parse the string.
*/
public void parseBencode()
{
final Stack<Character> stack = new Stack<Character>();
List<Object> list = new LinkedList<Object>();
Stack<List<Object>> lists = new Stack<List<Object>>();

Map<String, Object> map = new LinkedHashMap<String, Object>();
Stack<Map<String, Object>> maps = new Stack<Map<String, Object>>();
final Stack<String> keyStack = new Stack<String>();
String key = null;
while (currentPosition != contents.length())
{
final char ch = contents.charAt(currentPosition);
final char currentLoop = (stack.size() == 0 ? 'x' : stack.peek());
final Type currentType = parseType(ch, currentLoop);

switch (currentType)
{
case DIRECTORY:
if (currentLoop == 'd')
{
Map<String, Object> currentMap = new LinkedHashMap<String, Object>();
currentMap.putAll(map);
maps.add(currentMap);
keyStack.add(key);
key = null;
}

map = new LinkedHashMap<String, Object>();
currentPosition++;
stack.push('d');
break;

case LIST:
if (currentLoop == 'l')
{
List<Object> currentList = new LinkedList<Object>();
currentList.addAll(list);
lists.add(currentList);
}

list = new LinkedList<Object>();
currentPosition++;
stack.push('l');
break;

case NUMBER:
final Number number;
try
{
number = parseNumber();
}
catch (Exception e)
{
System.out.println(contents.substring(currentPosition));
e.printStackTrace();
return;
}

if (currentLoop == 'l')
{
list.add(number);
}
else if (currentLoop == 'd')
{
map.put(key, number);
key = null;
}
else
{
System.out.println(number);
}
break;

case STRING:
final String string;
try
{
string = parseString();

if (currentLoop == 'l')
{
list.add(string);
}
else if (currentLoop == 'd')
{
if (key == null)
{
key = string;
}
else
{
map.put(key, string == null ? "" : string);
key = null;
}
}
else
{
System.out.println(string);
}
}
catch (Exception e)
{
System.out.println(contents.substring(currentPosition));
e.printStackTrace();
return;
}
break;

case ENDLIST:
stack.pop();
final char innerLoopList = (stack.size() == 0 ? 'x' : stack.peek());
if (innerLoopList == 'd')
{
map.put(key, list);
key = null;
}
else if (innerLoopList == 'l')
{
final List<Object> newList = new LinkedList<Object>(list);
final List<Object> oldList = lists.firstElement();
list = new LinkedList<Object>();
list.addAll(oldList);
list.add(newList);
}
else
{
System.out.println(list);
}

currentPosition++;
break;

case ENDDIR:
stack.pop();

final char innerLoopMap = (stack.size() == 0 ? 'x' : stack.peek());
if (innerLoopMap == 'l')
{
list.add(map);
}
else if (innerLoopMap == 'd')
{
final Map<String, Object> newMap = new LinkedHashMap<String, Object>(map);
final Map<String, Object> oldMap = maps.firstElement();
map = new LinkedHashMap<String, Object>();
map.putAll(oldMap);
map.put(keyStack.pop(), newMap);
}
else
{
System.out.println(map);
}

currentPosition++;
break;
}
}
}

/**
* Get the number at current position.
* @return The number.
* @throws NumberFormatException
*/
private Number parseNumber() throws NumberFormatException
{
final String number = contents.substring(
++currentPosition,
contents.indexOf('e', currentPosition++));
currentPosition += number.length();
if (number.length() > 5)
{
return parseToLong(number);
}
else
{
return parseToInt(number);
}
}

/**
* Parse the string.
* @return The string.
* @throws InvalidStringException
*/
private String parseString()
throws InvalidStringException
{
final int breakPosition = contents.indexOf(':',
currentPosition);
if (breakPosition == -1)
{
throw new InvalidStringException();
}

final String numberToParse = contents.substring(
currentPosition++, breakPosition);
final int number = parseToInt(numberToParse);
currentPosition += numberToParse.length();

final StringBuilder stb = new StringBuilder();

if (number > 0)
{
for (int i = 1; i <= number; i++)
{
stb.append(contents.charAt(currentPosition));
if (i != number)
{
currentPosition++;
}
}
currentPosition++;
}

return stb.toString();
}

/**
* Get the type by given character.
* @param c The character to represent the type.
* @param currentLoop The current loop character.
* @return The type.
*/
private static Type parseType(char c, char currentLoop)
{
if (c == 'd')
{
return Type.DIRECTORY;
}
else if (c == 'l')
{
return Type.LIST;
}
else if (c == 'i')
{
return Type.NUMBER;
}
else if (c == 'e' && currentLoop == 'l')
{
return Type.ENDLIST;
}
else if (c == 'e' && currentLoop == 'd')
{
return Type.ENDDIR;
}
else
{
return Type.STRING;
}
}

/**
* Convert string to long
* @param str The string to convert.
* @return The long value.
*/
public static Long parseToLong(String str) throws NumberFormatException
{
if (str == null || str.length() == 0)
{
return 0l;
}
return Long.parseLong(str);
}

/**
* Convert string to integer.
* @param str The string to convert.
* @return The integer value.
*/
public static Integer parseToInt(String str) throws NumberFormatException
{
if (str == null || str.length() == 0)
{
return 0;
}
return Integer.parseInt(str);
}

/**
* @author Kartik Modi
* @email eng.kartik@gmail.com
* Types define for the Bencode.
*/
private enum Type
{
NUMBER, DIRECTORY, LIST, STRING, ENDLIST, ENDDIR
}

/**
* Main method.
*/
public static void main(String arr[]) throws IOException
{
final ParsingLogic logic = new ParsingLogic(CommonUtils.parseFileToString(new File("C:\\Users\\Kartik\\AppData\\Roaming\\uTorrent\\resume.dat")));
logic.parseBencode();
}

}