Package org.gudy.azureus2.core3.util

Source Code of org.gudy.azureus2.core3.util.BEncoder

/*
* BEncoder.java
*
* Created on June 4, 2003, 10:17 PM
* Copyright (C) 2003, 2004, 2005, 2006 Aelitis, All Rights Reserved.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
*
* AELITIS, SAS au capital de 46,603.30 euros
* 8 Allee Lenotre, La Grille Royale, 78600 Le Mesnil le Roi, France.
*/

package org.gudy.azureus2.core3.util;

import java.io.*;
import java.net.URLEncoder;
import java.nio.ByteBuffer;
import java.util.*;

import org.gudy.azureus2.core3.xml.util.XUXmlWriter;

/**
* A set of utility methods to encode a Map into a bencoded array of byte.
* integer are represented as Long, String as byte[], dictionnaries as Map, and list as List.
*
* @author  TdC_VgA
*/
public class
BEncoder
{   
  private static final int BUFFER_DOUBLE_LIMIT  = 256*1024;
 
  private static final byte[] MINUS_1_BYTES = "-1".getBytes();
 
  private static volatile int non_ascii_logs;

    public static byte[]
    encode(
      Map object )
   
      throws IOException
    {
       return( encode( object, false ));
    }   
   
    public static byte[]
    encode(
      Map   object,
      boolean url_encode )
   
      throws IOException
    {
      BEncoder encoder = new BEncoder(url_encode);
     
      encoder.encodeObject( object);
     
      return( encoder.toByteArray());
   
   
    private byte[]    current_buffer    = new byte[256];
    private int      current_buffer_pos  = 0;
    private byte[][]  old_buffers;   
   
    private byte[]    int_buffer      = new byte[12];
   
    private boolean  url_encode;
   
    private
    BEncoder(
      boolean  _url_encode )
    {
      url_encode  = _url_encode;
    }
       
    private boolean
  encodeObject(
    Object           object)
   
      throws IOException
  {
     
        if (object instanceof BEncodableObject) {
            object = ((BEncodableObject)object).toBencodeObject();
        }

        if ( object instanceof String || object instanceof Float){
         
            String tempString = (object instanceof String) ? (String)object : String.valueOf((Float)object);

              // usually this is simpler to encode by hand as chars < 0x80 map directly in UTF-8
           
            boolean  simple = true;
           
            int  char_count = tempString.length();
           
            byte[]  encoded = new byte[char_count];
           
            for (int i=0;i<char_count;i++){
             
              char c = tempString.charAt(i);
             
              if ( c < 0x80 ){
               
                encoded[i] = (byte)c;
               
              }else{
               
                simple = false;
               
                break;
              }
            }
           
            if ( simple ){
             
               writeInt( char_count );
             
              writeChar( ':' );

              writeBytes( encoded );
             
            }else{
                          
              ByteBuffer  bb   = Constants.DEFAULT_CHARSET.encode( tempString );          
             
              writeInt( bb.limit() );
             
              writeChar(':');
             
              writeByteBuffer(bb );
            }
           
        }else if(object instanceof Map){
         
            Map tempMap = (Map)object;
           
            SortedMap tempTree = null;
           
              // unfortunately there are some occasions where we want to ensure that
              // the 'key' of the map is not mangled by assuming its UTF-8 encodable.
              // In particular the response from a tracker scrape request uses the
              // torrent hash as the KEY. Hence the introduction of the type below
              // to allow the constructor of the Map to indicate that the keys should
              // be extracted using a BYTE_ENCODING
             
            boolean  byte_keys = object instanceof ByteEncodedKeyHashMap;
           
            //write the d           
            writeChar('d');
           
            //are we sorted?
            if ( tempMap instanceof TreeMap ){
             
                tempTree = (TreeMap)tempMap;
               
            }else{
                tempTree = new TreeMap(tempMap);
            }           
                  
            Iterator  it = tempTree.entrySet().iterator();
           
            while( it.hasNext()){
             
              Map.Entry  entry = (Map.Entry)it.next();
     
              Object o_key = entry.getKey();
                            
              Object value = entry.getValue();

              if (value != null)
        {
          if (o_key instanceof byte[])
          {
            encodeObject(o_key);
            if (!encodeObject(value))
              encodeObject("");
          } else if(o_key instanceof String)
          {
            String key = (String) o_key;
            if (byte_keys)
            {
              try
              {
                encodeObject(Constants.BYTE_CHARSET.encode(key));
                if (!encodeObject(value))
                  encodeObject("");
              } catch (UnsupportedEncodingException e)
              {
                throw (new IOException("BEncoder: unsupport encoding: " + e.getMessage()));
              }
            } else
            {
                // if we put non-ascii chars in as keys we can get horrible expanding
                // config issues as we cycle through decode/encode cycles with certain
                // characters
             
              if ( Constants.IS_CVS_VERSION ){
                char[]  chars = key.toCharArray();
               
                for ( char c: chars ){

                  if (c >= '\u0080'){

                    if ( non_ascii_logs < 50 ){
                     
                      non_ascii_logs++;
                     
                      Debug.out( "Non-ASCII key: " + key );
                    }
                   
                    break;
                  }
                }
              }
              encodeObject(key); // Key goes in as UTF-8
              if (!encodeObject(value))
                encodeObject("");
            }
          } else
            Debug.out( "Attempt to encode an unsupported map key type: " + object.getClass() + ";value=" + object);
        }    
            }
           
            writeChar('e');
           
           
        }else if(object instanceof List){
         
            List tempList = (List)object;
           
              //write out the l
           
            writeChar('l');                                  
           
            for(int i = 0; i<tempList.size(); i++){
               
              encodeObject( tempList.get(i));                           
            }  
           
            writeChar('e');                         
           
        }else if(object instanceof Long){
         
            Long tempLong = (Long)object;        
            //write out the l      
            writeChar('i');
            writeLong(tempLong.longValue());
            writeChar('e');
         }else if(object instanceof Integer){
          
      Integer tempInteger = (Integer)object;        
      //write out the l      
      writeChar('i');
      writeInt(tempInteger.intValue());
      writeChar('e');
     
       }else if(object instanceof byte[]){
        
            byte[] tempByteArray = (byte[])object;
            writeInt(tempByteArray.length);
            writeChar(':');
            if ( url_encode ){
              writeBytes(URLEncoder.encode(new String(tempByteArray, Constants.BYTE_ENCODING), Constants.BYTE_ENCODING ).getBytes());
            }else{
              writeBytes(tempByteArray);
            }
           
       }else if(object instanceof ByteBuffer ){
        
           ByteBuffer  bb = (ByteBuffer)object;
           writeInt(bb.limit());
           writeChar(':');
            writeByteBuffer(bb);
           
       }else if ( object == null ){
        
           // ideally we'd bork here but I don't want to run the risk of breaking existing stuff so just log
        
        
         Debug.out( "Attempt to encode a null value: sofar=" + getEncodedSoFar());
         return false;
        
       }else{
         
        
         Debug.out( "Attempt to encode an unsupported entry type: " + object.getClass() + ";value=" + object);
         return false;
       }
       
        return true;
    }
   
    private void
    writeChar(
      char    c )
     {
      int rem = current_buffer.length - current_buffer_pos;
     
      if ( rem > 0 ){
       
        current_buffer[current_buffer_pos++] = (byte)c;
       
      }else{
       
           int  next_buffer_size = current_buffer.length < BUFFER_DOUBLE_LIMIT?(current_buffer.length << 1):(current_buffer.length + BUFFER_DOUBLE_LIMIT );

        byte[]  new_buffer = new byte[ next_buffer_size ];
          
        new_buffer[ 0 ] = (byte)c;

        if ( old_buffers == null ){
         
          old_buffers = new byte[][]{ current_buffer };
         
        }else{
         
          byte[][] new_old_buffers = new byte[old_buffers.length+1][];
         
          System.arraycopy( old_buffers, 0, new_old_buffers, 0, old_buffers.length );
         
          new_old_buffers[ old_buffers.length ] = current_buffer;
         
          old_buffers = new_old_buffers;
        }
       
        current_buffer    = new_buffer;
        current_buffer_pos   = 1;
       }
     }
    
    private void
    writeInt(
      int    i )
    {
        // we get a bunch of -1 values, optimise
     
      if ( i == -1 ){
       
        writeBytes( MINUS_1_BYTES );
       
        return;
      }
     
      int start = intToBytes( i );
          
      writeBytes( int_buffer, start, 12 - start );
    }
   
    private void
    writeLong(
      long  l )
    {
       if ( l <= Integer.MAX_VALUE && l >= Integer.MIN_VALUE ){
       
        writeInt((int)l);
       
      }else{
       
        writeBytes(Long.toString( l ).getBytes());
      }
    }
   
    private void
    writeBytes(
      byte[]      bytes )
    {
      writeBytes( bytes, 0, bytes.length );
    }
   
    private void
    writeBytes(
      byte[]      bytes,
      int        offset,
      int        length )
    {
      int rem = current_buffer.length - current_buffer_pos;
     
      if ( rem >= length ){
       
        System.arraycopy( bytes, offset, current_buffer, current_buffer_pos, length );
       
        current_buffer_pos += length;
       
      }else{
       
        if ( rem > 0 ){
         
          System.arraycopy( bytes, offset, current_buffer, current_buffer_pos, rem );
 
          length -= rem;
        }
           
        int  next_buffer_size = current_buffer.length < BUFFER_DOUBLE_LIMIT?(current_buffer.length << 1):(current_buffer.length + BUFFER_DOUBLE_LIMIT );
           
        byte[]  new_buffer = new byte[ Math.max( next_buffer_size, length + 512 ) ];
                 
        System.arraycopy( bytes, offset + rem, new_buffer, 0, length );

        if ( old_buffers == null ){
         
          old_buffers = new byte[][]{ current_buffer };
         
        }else{
         
          byte[][] new_old_buffers = new byte[old_buffers.length+1][];
         
          System.arraycopy( old_buffers, 0, new_old_buffers, 0, old_buffers.length );
         
          new_old_buffers[ old_buffers.length ] = current_buffer;
         
          old_buffers = new_old_buffers;
        }
       
        current_buffer    = new_buffer;
        current_buffer_pos   = length;
       }  
    }
   
    private void
  writeByteBuffer(
    ByteBuffer    bb )
    {
      writeBytes( bb.array(), bb.arrayOffset() + bb.position(), bb.remaining());
    }

    private String
    getEncodedSoFar()
    {
      return( new String( toByteArray()));
    }
   
    private byte[]
    toByteArray()
    {
      if ( old_buffers == null ){
       
        byte[]  res = new byte[current_buffer_pos];
       
        System.arraycopy( current_buffer, 0, res, 0, current_buffer_pos );
       
        // System.out.println( "-> " + current_buffer_pos );
       
        return( res );
       
      }else{
       
        int  total = current_buffer_pos;
       
        for (int i=0;i<old_buffers.length;i++){
         
          total += old_buffers[i].length;
        }
       
        byte[] res = new byte[total];
       
        int  pos = 0;
       
        //String str = "";
       
        for (int i=0;i<old_buffers.length;i++){

          byte[] buffer = old_buffers[i];
         
          int  len = buffer.length;
         
          System.arraycopy( buffer, 0, res, pos, len );
         
          pos += len;
         
          //str += (str.length()==0?"":",") + len;
        }
       
          System.arraycopy( current_buffer, 0, res, pos, current_buffer_pos );
          
        //System.out.println( "-> " + str + "," + current_buffer_pos );

          return( res );
      }
    }
                  
   
    private static Object
    normaliseObject(
      Object    o )
    {
      if ( o instanceof Integer ){
           o = new Long(((Integer)o).longValue());
        }else if ( o instanceof Boolean ){
           o = new Long(((Boolean)o).booleanValue()?1:0);
        }else if ( o instanceof Float ){
           o = String.valueOf((Float)o);
         }else if ( o instanceof byte[] ){       
           try{
             o = new String((byte[])o,"UTF-8");     
           }catch( Throwable e ){
           }
         }
     
      return( o );
    }
   
    public static boolean isEncodable(Object toCheck) {
    if (toCheck instanceof Integer || toCheck instanceof Long || toCheck instanceof Boolean || toCheck instanceof Float || toCheck instanceof byte[] || toCheck instanceof String || toCheck instanceof BEncodableObject)
      return true;
    if (toCheck instanceof Map)
    {
      for (Iterator it = ((Map) toCheck).keySet().iterator(); it.hasNext();)
      {
        Map.Entry entry = (Map.Entry) it.next();
        Object key = entry.getKey();
        if (!(key instanceof String || key instanceof byte[]) || !isEncodable(entry.getValue()))
          return false;
      }
      return true;
    }
    if (toCheck instanceof List)
    {
      for (Iterator it = ((List) toCheck).iterator(); it.hasNext();)
        if (!isEncodable(it.next()))
          return false;
      return true;
    }
    return false;
  }
   
   
    public static boolean
    objectsAreIdentical(
      Object    o1,
      Object    o2 )
    {
      if ( o1 == null && o2 == null ){
       
        return( true );
       
      }else if ( o1 == null || o2 == null ){
       
        return( false );
      }
     
        if ( o1.getClass() != o2.getClass()){
          
        if (   ( o1 instanceof Map && o2 instanceof Map ) ||
            ( o1 instanceof List && o2 instanceof List )){
         
          // things actually OK
         
        }else{
         
          o1 = normaliseObject( o1 );
          o2 = normaliseObject( o2 );
          
          if ( o1.getClass() != o2.getClass()){
                       
            Debug.out( "Failed to normalise classes " + o1.getClass() + "/" + o2.getClass());
             
            return( false );
          }
          }
      }
     
      if (   o1 instanceof Long ||
          o1 instanceof String ){   
       
        return( o1.equals( o2 ));
       
       }else if ( o1 instanceof byte[] ){
        
         return( Arrays.equals((byte[])o1,(byte[])o2 ));
                
      }else if ( o1 instanceof List ){
       
        return( listsAreIdentical((List)o1,(List)o2));
       
         }else if ( o1 instanceof Map ){
                
        return( mapsAreIdentical((Map)o1,(Map)o2));
       
         }else if (   o1 instanceof Integer ||
            o1 instanceof Boolean ||
            o1 instanceof Float ||
            o1 instanceof ByteBuffer ){
       
        return( o1.equals( o2 ));

      }else{
       
        Debug.out( "Invalid type: " + o1 );
       
        return( false );
      }
    }
   
    public static boolean
  listsAreIdentical(
    List  list1,
    List  list2 )
    {
      if ( list1 == null && list2 == null ){
       
        return( true );
       
      }else if ( list1 == null || list2 == null ){
       
        return( false );
      }
     
      if ( list1.size() != list2.size()){
       
        return( false );
      }
     
      for ( int i=0;i<list1.size();i++){
       
        if ( !objectsAreIdentical( list1.get(i), list2.get(i))){
         
          return( false );
        }
      }
     
      return( true );
    }
   
    public static boolean
  mapsAreIdentical(
    Map  map1,
    Map  map2 )
  {
      if ( map1 == null && map2 == null ){
       
        return( true );
       
      }else if ( map1 == null || map2 == null ){
       
        return( false );
      }
     
      if ( map1.size() != map2.size()){
       
        return( false );
      }
     
      Iterator  it = map1.keySet().iterator();
     
      while( it.hasNext()){
       
        Object  key = it.next();
       
        Object  v1 = map1.get(key);
        Object  v2 = map2.get(key);
       
        if ( !objectsAreIdentical( v1, v2 )){
         
          return( false );
        }
      }
     
      return( true );
   
   
    public static Map
    cloneMap(
      Map    map )
    {
      if ( map == null ){
       
        return( null );
      }
     
      Map res = new TreeMap();
     
      Iterator  it = map.entrySet().iterator();
     
      while( it.hasNext()){
       
        Map.Entry  entry = (Map.Entry)it.next();
       
        Object  key   = entry.getKey();
        Object  value  = entry.getValue();

          // keys must be String (or very rarely byte[])
       
        if ( key instanceof byte[] ){
         
          key = ((byte[])key).clone();
        }
       
        res.put( key, clone( value ));
      }
     
      return( res );
    }
   
    public static List
    cloneList(
      List    list )
    {
      if ( list == null ){
       
        return( null );
      }
     
      List  res = new ArrayList(list.size());
     
      Iterator  it = list.iterator();
     
      while( it.hasNext()){
       
        res.add( clone( it.next()));
      }
     
      return( res );
    }
   
    public static Object
    clone(
      Object  obj )
    {
      if ( obj instanceof List ){
       
        return( cloneList((List)obj));
       
      }else if ( obj instanceof Map ){
       
        return( cloneMap((Map)obj));
       
      }else if ( obj instanceof byte[]){
       
        return(((byte[])obj).clone());
       
      }else{
          // assume immutable - String,Long etc
       
        return( obj );
      }
    }
   
    public static StringBuffer
    encodeToXML(
      Map      map,
      boolean    simple )
    {
       XMLEncoder writer = new XMLEncoder();
 
       return( writer.encode( map, simple ));
    }   
   
      /*
       * The following code is from Integer.java as we don't want to
       */
    final static byte[] digits = {
      '0' , '1' , '2' , '3' , '4' , '5' ,
      '6' , '7' , '8' , '9' , 'a' , 'b' ,
      'c' , 'd' , 'e' , 'f' , 'g' , 'h' ,
      'i' , 'j' , 'k' , 'l' , 'm' , 'n' ,
      'o' , 'p' , 'q' , 'r' , 's' , 't' ,
      'u' , 'v' , 'w' , 'x' , 'y' , 'z'
        };
   
    final static byte [] DigitTens = {
      '0', '0', '0', '0', '0', '0', '0', '0', '0', '0',
      '1', '1', '1', '1', '1', '1', '1', '1', '1', '1',
      '2', '2', '2', '2', '2', '2', '2', '2', '2', '2',
      '3', '3', '3', '3', '3', '3', '3', '3', '3', '3',
      '4', '4', '4', '4', '4', '4', '4', '4', '4', '4',
      '5', '5', '5', '5', '5', '5', '5', '5', '5', '5',
      '6', '6', '6', '6', '6', '6', '6', '6', '6', '6',
      '7', '7', '7', '7', '7', '7', '7', '7', '7', '7',
      '8', '8', '8', '8', '8', '8', '8', '8', '8', '8',
      '9', '9', '9', '9', '9', '9', '9', '9', '9', '9',
      } ;

    final static byte [] DigitOnes = {
      '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
      '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
      '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
      '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
      '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
      '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
      '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
      '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
      '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
      '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
      } ;
       
      /**
       * writes to int_buffer and returns start position in buffer (always runs to end of buffer)
       * @param i
       * @return
       */
   
    private int
    intToBytes(
      int   i )
    {
        int q, r;
        int charPos = 12;
        byte sign = 0;

        if (i < 0) {
            sign = '-';
            i = -i;
        }

        // Generate two digits per iteration
        while (i >= 65536) {
            q = i / 100;
        // really: r = i - (q * 100);
            r = i - ((q << 6) + (q << 5) + (q << 2));
            i = q;
            int_buffer [--charPos] = DigitOnes[r];
            int_buffer [--charPos] = DigitTens[r];
        }

        // Fall thru to fast mode for smaller numbers
        // assert(i <= 65536, i);
        for (;;) {
            q = (i * 52429) >>> (16+3);
            r = i - ((q << 3) + (q << 1))// r = i-(q*10) ...
            int_buffer [--charPos] = digits [r];
            i = q;
            if (i == 0) break;
        }
        if (sign != 0) {
          int_buffer [--charPos] = sign;
        }
        return charPos;
    }
   
    protected static class
    XMLEncoder
      extends XUXmlWriter
    {
      protected
      XMLEncoder()
      {
      }
     
      protected StringBuffer
      encode(
        Map    map,
        boolean  simple )
      {
        StringWriter  writer = new StringWriter(1024);
       
        setOutputWriter( writer );
       
        setGenericSimple( simple );
       
        writeGeneric( map );
       
        flushOutputStream();
       
        return( writer.getBuffer());
      }
    }
}
TOP

Related Classes of org.gudy.azureus2.core3.util.BEncoder

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.