gt:     		return( null );
context: /*
   * 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.nio.charset.Charset;
  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();
  	
      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 void 
  	encodeObject(
  		Object 					object) 
      
      	throws IOException
  	{
      	
          if ( object instanceof String || object instanceof Float){
          	
              String tempString = (object instanceof String) ? (String)object : String.valueOf((Float)object);
  
              ByteBuffer	bb 	= ByteBuffer.wrap(tempString.getBytes("UTF-8"));           
              
              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 );
  	      				
  						encodeObject( value );
  
  					}else{
  						
  						String	key = (String)o_key;
  					
  		                if ( byte_keys ){
  		                		   		
  		   					try{
  		  					
  		   						encodeObject( Charset.forName( "ISO-8859-1" ).encode(key));
  		      				
  		   						encodeObject( tempMap.get(key));
  		      		
  		    				}catch( UnsupportedEncodingException e ){
  		                		
  		    					throw( new IOException( "BEncoder: unsupport encoding: " + e.getMessage()));
  		    				}
  		
  		                }else{                 
  	
  		                	encodeObject( key );	// Key goes in as UTF-8
  		      				
  		                	encodeObject( value);
  	    				}   
  					}
                  }     
              }
              
              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, "UTF-8"), "UTF-8" ).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
      	   
      	   System.err.println( "Attempt to encode a null value" );
      	   
         }else{
          	
      	   System.err.println( "Attempt to encode an unsupported entry type: " + object.getClass() + ";value=" + object);
         }
      }
      
      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 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
      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{
      		
      		//( "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 ){
      		
  