/*
* BeDecoder.java
*
*/
packagecom.vista.test;
importjava.io.*;
importjava.nio.ByteBuffer;
importjava.nio.CharBuffer;
importjava.nio.charset.Charset;
importjava.util.ArrayList;
importjava.util.HashMap;
importjava.util.Iterator;
importjava.util.List;
importjava.util.Map;
/**
* A set of utility methods to decode a bencoded array of byte into a Map.
* integer are represented as Long, String as byte[], dictionnaries as Map, and list as List.
*
*/
publicclassBDecoder
{
//字符集
publicstaticfinalString BYTE_ENCODING ="UTF8";
publicstaticCharset BYTE_CHARSET;
static
{
try
{
BYTE_CHARSET = Charset.forName(BYTE_ENCODING);
}
catch( Throwable e )
{
e.printStackTrace();
}
}
privatestaticfinalbooleanTRACE =true;
privatebooleanrecovery_mode;
publicstaticMap decode(BufferedInputStream is)throwsException
{//解码
return(newBDecoder().decodeStream( is ));
}
publicBDecoder()
{
}
publicMap decodeStream(BufferedInputStream data )throwsException
{
Object res = decodeInputStream(newBDecoderInputStreamStream(data),0);//0指定递归层次从第一层开始
if( res ==null)
{
throw(newException("BDecoder: zero length file"));
}
elseif( !(resinstanceofMap ))
{
throw(newException("BDecoder: top level isn't a Map"));
}
return((Map)res );
}
/**
*
* @param dbis
* @param nesting 递归层次
* @throws Exception
*/
privateObject decodeInputStream(BDecoderInputStream dbis,intnesting )throwsException
{
if(nesting ==0&& !dbis.markSupported())
{
thrownewIOException("InputStream must support the mark() method");
}
//set a mark
dbis.mark(Integer.MAX_VALUE);
//read a byte
inttempByte = dbis.read();//读一个字节
//decide what to do
switch(tempByte)
{
case'd':
{//是字典
//create a new dictionary object
Map tempMap =newHashMap();
try
{
//get the key
byte[] tempByteArray =null;
while((tempByteArray = (byte[]) decodeInputStream(dbis, nesting+1)) !=null)
{
//decode some more
Object value = decodeInputStream(dbis,nesting+1);//读值
// value interning is too CPU-intensive, let's skip that for now
//if(value instanceof byte[] && ((byte[])value).length
//value = StringInterner.internBytes((byte[])value);
// keys often repeat a lot - intern to save space
String key =null;
if( key ==null)
{
CharBuffer cb = BYTE_CHARSET.decode(ByteBuffer.wrap(tempByteArray));
key =newString(cb.array(),0,cb.limit());//键
}
if( TRACE )
{
System.out.println( key +"->"+ value +";");
}
// recover from some borked encodings that I have seen whereby the value has
// not been encoded. This results in, for example,
// 18:azureus_propertiesd0:e
// we only get null back here if decoding has hit an 'e' or end-of-file
// that is, there is no valid way for us to get a null 'value' here
if( value ==null)
{
//Debug.out( "Invalid encoding - value not serialsied for '" + key + "' - ignoring" );
break;
}
tempMap.put( key, value);//放入结果集中
}
dbis.mark(Integer.MAX_VALUE);
tempByte = dbis.read();
dbis.reset();
if( nesting >0&& tempByte == -1)
{
throw(newException("BDecoder: invalid input data, 'e' missing from end of dictionary"));
}
}catch( Throwable e )
{
if( !recovery_mode )
{
if( einstanceofIOException )
{
throw((IOException)e);
}
throw(newIOException(e.getMessage()));
}
}
returntempMap;
}
case'l':
{
//create the list
ArrayList tempList =newArrayList();
try
{
//create the key
Object tempElement =null;
while((tempElement = decodeInputStream(dbis, nesting+1)) !=null)
{
//add the element
tempList.add(tempElement);//读取列表元素并加入列表中
}
tempList.trimToSize();
dbis.mark(Integer.MAX_VALUE);
tempByte = dbis.read();
dbis.reset();
if( nesting >0&& tempByte == -1)
{
throw(newException("BDecoder: invalid input data, 'e' missing from end of list"));
}
}
catch( Throwable e )
{
if( !recovery_mode )
{
if( einstanceofIOException )
{
throw((IOException)e);
}
throw(newIOException(e.getMessage()));
}
}
//return the list
returntempList;
}
case'e':
case-1:
returnnull;//当前结束
case'i':
returnnewLong(getNumberFromStream(dbis,'e'));//整数
case'0':
case'1':
case'2':
case'3':
case'4':
case'5':
case'6':
case'7':
case'8':
case'9':
//move back one
dbis.reset();
//get the string
returngetByteArrayFromStream(dbis);//读取指定长度字符串
default:
{
intrem_len = dbis.available();
if( rem_len >256)
{
rem_len =256;
}
byte[] rem_data =newbyte[rem_len];
dbis.read( rem_data );
throw(newException("BDecoder: unknown command '"+ tempByte +", remainder = "+newString( rem_data )));
}
}
}
/** only create the array once per decoder instance (no issues with recursion as it's only used in a leaf method)
*/
privatefinalchar[] numberChars =newchar[32];
privatelonggetNumberFromStream(BDecoderInputStream dbis,charparseChar)throwsIOException
{
inttempByte = dbis.read();
intpos =0;
while((tempByte != parseChar) && (tempByte >=0))
{//读取整数字节,直到终结字符'e'
numberChars[pos++] = (char)tempByte;
if( pos == numberChars.length )
{
throw(newNumberFormatException("Number too large: "+newString(numberChars,0,pos) +""));
}
tempByte = dbis.read();
}
//are we at the end of the stream?
if(tempByte <0)
{
return-1;
}
elseif( pos ==0)
{
// support some borked impls that sometimes don't bother encoding anything
return(0);
}
return( parseLong( numberChars,0, pos ));//转换为Long型整数
}
publicstaticlongparseLong(char[] chars,intstart,intlength )
{//转换为Long型整数
longresult =0;
booleannegative =false;
inti = start;
intmax = start + length;
longlimit;
if( length >0)
{
if( chars[i] =='-')
{
negative =true;
limit = Long.MIN_VALUE;
i++;
}
else
{
limit = -Long.MAX_VALUE;
}
if( i
{
intdigit = chars[i++] -'0';
if( digit <0|| digit >9)
{
thrownewNumberFormatException(newString(chars,start,length));
}
else
{
result = -digit;
}
}
longmultmin = limit /10;
while( i
{
// Accumulating negatively avoids surprises near MAX_VALUE
intdigit = chars[i++] -'0';
if( digit <0|| digit >9)
{
thrownewNumberFormatException(newString(chars,start,length));
}
if( result
{
thrownewNumberFormatException(newString(chars,start,length));
}
result *=10;
if( result
{
thrownewNumberFormatException(newString(chars,start,length));
}
result -= digit;
}
}
else
{
thrownewNumberFormatException(newString(chars,start,length));
}
if( negative )
{
if( i > start+1)
{
returnresult;
}
else
{/* Only got "-" */
thrownewNumberFormatException(newString(chars,start,length));
}
}
else
{
return-result;
}
}
privatebyte[] getByteArrayFromStream(BDecoderInputStream dbis )throwsIOException
{
intlength = (int) getNumberFromStream(dbis,':');
if(length <0)
{
returnnull;
}
// note that torrent hashes can be big (consider a 55GB file with 2MB pieces
// this generates a pieces hash of 1/2 meg
if( length >8*1024*1024)
{
throw(newIOException("Byte array length too large ("+ length +")"));
}
byte[] tempArray =newbyte[length];
intcount =0;
intlen =0;
//get the string
while(count != length && (len = dbis.read(tempArray, count, length - count)) >0)
{
count += len;
}
if( count != tempArray.length )
{
throw(newIOException("BDecoder::getByteArrayFromStream: truncated"));
}
returntempArray;
}
publicvoidsetRecoveryMode(booleanr )
{
recovery_mode = r;
}
publicstaticvoidprint(PrintWriter writer,Object obj )
{
print( writer, obj,"",false);
}
privatestaticvoidprint(PrintWriter writer,Object obj,String indent,booleanskip_indent )
{
String use_indent = skip_indent?"":indent;
if( objinstanceofLong )
{
writer.println( use_indent + obj );
}
elseif( objinstanceofbyte[])
{
byte[] b = (byte[])obj;
if( b.length==20)
{
writer.println( use_indent +" { "+ ByteFormatter.nicePrint( b )+" }");
}
elseif( b.length <64)
{
writer.println(newString(b) +" ["+ ByteFormatter.encodeString( b ) +"]");
}else{
writer.println("[byte array length "+ b.length );
}
}elseif( objinstanceofString )
{
writer.println( use_indent + obj );
}
elseif( objinstanceofList )
{
List l = (List)obj;
writer.println( use_indent +"[");
for(inti=0;i
{
writer.print( indent +" ("+ i +") ");
print( writer, l.get(i), indent +" ",true);
}
writer.println( indent +"]");
}
else
{
Map m = (Map)obj;
Iterator it = m.keySet().iterator();
while( it.hasNext())
{
String key = (String)it.next();
if( key.length() >256)
{
writer.print( indent + key.substring(0,256) +" = ");
}
else
{
writer.print( indent + key +" = ");
}
print( writer, m.get(key), indent +" ",true);
}
}
}
privatestaticvoidprint(File f,File output)
{
try
{
BDecoder decoder =newBDecoder();//×××
PrintWriter pw =newPrintWriter(newFileWriter( output ));//输出结果
print( pw, decoder.decodeStream(newBufferedInputStream(newFileInputStream( f ))));
pw.flush();
}
catch( Throwable e )
{
e.printStackTrace();
}
}
privateinterfaceBDecoderInputStream
{
publicintread()throwsIOException;
publicintread(byte[] buffer)throwsIOException;
publicintread(byte[] buffer,intoffset,intlength )throwsIOException;
publicintavailable()throwsIOException;
publicbooleanmarkSupported();
publicvoidmark(intlimit );
publicvoidreset()throwsIOException;
}
privateclassBDecoderInputStreamStreamimplementsBDecoderInputStream
{
finalprivateBufferedInputStream is;
privateBDecoderInputStreamStream(BufferedInputStream _is )
{
is = _is;
}
/**
* 从此输入流中读取下一个数据字节。返回一个 0 到 255 范围内的 int 字节值。
* 如果因为已经到达流末尾而没有字节可用,则返回 -1。
* 在输入数据可用、检测到流末尾或抛出异常之前,此方法将一直阻塞。
*/
publicintread()throwsIOException
{
return( is.read());
}
/**
* 从此输入流中将 byte.length 个字节的数据读入一个 byte 数组中。在某些输入可用之前,此方法将阻塞。
*/
publicintread(byte[] buffer )throwsIOException
{
return( is.read( buffer ));
}
/**
* 从此字节输入流中给定偏移量处开始将各字节读取到指定的 byte 数组中。
*/
publicintread(byte[] buffer,intoffset,intlength)throwsIOException
{
return( is.read( buffer, offset, length ));
}
/**
* 返回可以从此输入流读取(或跳过)、且不受此输入流接下来的方法调用阻塞的估计字节数。
*/
publicintavailable()throwsIOException
{
return( is.available());
}
/**
* 测试此输入流是否支持 mark 和 reset 方法。
*/
publicbooleanmarkSupported()
{
return( is.markSupported());
}
/**
* 在输入流中的当前位置上作标记。reset 方法的后续调用将此流重新定位在最后标记的位置上,以便后续读取操作重新读取相同的字节。
* @param limit 在标记位置变为无效之前可以读取字节的最大限制。
*/
publicvoidmark(intlimit )
{
is.mark( limit );
}
/**
* 将此流重新定位到对此输入流最后调用 mark 方法时的位置。
*/
publicvoidreset()throwsIOException
{
is.reset();
}
}
publicstaticvoidmain(String[] args )
{
print(newFile("C:\\1001.torrent"),newFile("C:\\tables.txt"));
}
}