gt:         byte[] bencoded_list = new byte[total_length];
context: /*
   *  RUBTClient is a BitTorrent client written at Rutgers University for 
   *  instructional use.
   *  Copyright (C) 2008-2009  Robert Moore II
   *
   *  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 3 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, see <http://www.gnu.org/licenses/>.
   */
   
  
  import java.nio.ByteBuffer;
  import java.util.ArrayList;
  import java.util.HashMap;
  import java.util.Iterator;
  import java.util.TreeMap;
  
  /**
   * Parses a Bencoded byte array and returns a combination of {@code Map},
   * {@code List}, {@code ByteBuffer}, and {@code Integer} objects.
   * 
   * @author Robert Moore II
   *
   */
  public final class Bencoder2 
  {
  	/**
  	 * Indicates an invalid object type or bencoded object.
  	 */
      private static final int INVALID = -1;
      
      /**
       * Indicates that an object is a dictionary.
       */
      private static final int DICTIONARY = 0;
      
      /**
       * Indicates that an object is an integer.
       */
      private static final int INTEGER = 1;
      
      /**
       * Indicates an object is a byte string.
       */
      private static final int STRING = 2;
      
      /**
       * Indicates an object is a list.
       */
      private static final int LIST = 3;
      
      /*
       ********************************************
       ************ CONVENIENCE METHODS ***********
       ********************************************
       */
      
      /**
       * Extracts the bencoded 'info' dictionary from a metainfo torrent file.
       * @param torrent_file_bytes the bencoded metainfo dictionary.
       * @return a {@code ByteBuffer} containing the bencoded 'info' dictionary from the metainfo file.
       * @throws BencodingException if the 'info' key is not contained in the decoded dictionary.
       */
      public static final ByteBuffer getInfoBytes(byte[] torrent_file_bytes) throws BencodingException
      {
          Object[] vals = decodeDictionary(torrent_file_bytes,0);
          if(vals.length != 3 || vals[2] == null)
              throw new BencodingException("Exception: No info bytes found!");
          return (ByteBuffer)vals[2];
      }
      
      /*
       ********************************************
       ************ BDECODING METHODS *************
       ********************************************
       */
      
      /**
       * Decodes a bencoded object represented by the byte array.
       * @param bencoded_bytes the bencoded data to decode.
       * @return either a {@code Map}, {@code List}, {@code ByteBuffer}, or {@code Integer}.
       * @throws BencodingException if the bencoded data was improperly formatted.
       */
      public static final Object decode(byte[] bencoded_bytes) throws BencodingException
      {
          return decode(bencoded_bytes, 0)[1];
      }
      
      /**
       * Decodes a bencoded object represented by the byte array, starting at the specified offset.
       * @param bencoded_bytes the bencoded data to decode.
       * @param offset the offset into {@code bencoded_bytes} at which to start decoding.
       * @return a {@code Map}, {@code List}, {@code ByteBuffer}, or {@code Integer}.
       * @throws BencodingException if the bencoded object in {@code bencoded_bytes} at offset {@code offset} is incorrectly encoded. 
       */
      private static final Object[] decode(byte[] bencoded_bytes, int offset) throws BencodingException
      {
          switch(nextObject(bencoded_bytes, offset))
          {
          case DICTIONARY:
              return decodeDictionary(bencoded_bytes, offset);
          case LIST:
              return decodeList(bencoded_bytes, offset);
          case INTEGER:
              return decodeInteger(bencoded_bytes, offset);
          case STRING:
              return decodeString(bencoded_bytes, offset);
          default:
              return null;
          }
      }
      
      /**
       * Decodes an integer from the byte array.
       * @param bencoded_bytes the byte array of the bencoded integer.
       * @param offset the position of the 'i' indicating the start of the
       *        bencoded integer to be bdecoded.
       * @return an <code>Object[]</code> containing an <code>Integer</code> offset and the decoded
       *          <code>Integer</code>, in positions 0 and 1, respectively
       * @throws BencodingException if the bencoded integer in {@code bencoded_bytes} at offset {@code offset} is incorrectly encoded.
       */
      private static final Object[] decodeInteger(byte[] bencoded_bytes, int offset) throws BencodingException
      {
          StringBuffer int_chars = new StringBuffer();
          offset++;
          for(; bencoded_bytes[offset] != (byte)'e' && bencoded_bytes.length > (offset); offset++)
          {
              if((bencoded_bytes[offset] < 48 || bencoded_bytes[offset] > 57) && bencoded_bytes[offset] != 45)
                  throw new BencodingException("Expected an ASCII integer character, found " + (int)bencoded_bytes[offset]);
              int_chars.append((char)bencoded_bytes[offset]);
          }
          try 
          {
              offset++;   // Skip the 'e'
              return new Object[] {new Integer(offset),new Integer(Integer.parseInt(int_chars.toString()))};
          }
          catch(NumberFormatException nfe)
          {
              throw new BencodingException("Could not parse integer at position" + offset + ".\nInvalid character at position " + offset + ".");
          }
      }
      
      /**
       * Decodes a byte string from the byte array
       * @param bencoded_bytes the bencoded form of the byte string.
       * @param offset the offset into {@code bencoded_bytes} where the byte string begins.
       * @return an <code>Object[]</code> containing an <code>Integer</code> offset and the decoded
       *          byte string (as a {@code ByteBuffer}), in positions 0 and 1, respectively
       * @throws BencodingException if the bencoded object is incorrectly encoded.
       */
      private static final Object[] decodeString(byte[] bencoded_bytes, int offset) throws BencodingException
      {
          StringBuffer digits = new StringBuffer();
          while(bencoded_bytes[offset] > '/' && bencoded_bytes[offset] < ':')
          {
              digits.append((char)bencoded_bytes[offset++]);
          }
          if(bencoded_bytes[offset] != ':')
          {
              throw new BencodingException("Error: Invalid character at position " + offset + ".\nExpecting ':' but found '" + (char)bencoded_bytes[offset] + "'.");
          }
          offset++;
          int length = Integer.parseInt(digits.toString());
          byte[] byte_string = new byte[length];
          System.arraycopy(bencoded_bytes, offset, byte_string, 0, byte_string.length);
          return new Object[] {new Integer(offset+length), ByteBuffer.wrap(byte_string)};
      }
      
      /**
       * Decodes a list from the bencoded byte array.
       * @param bencoded_bytes the bencoded form of the list.
       * @param offset the offset into {@code bencoded_bytes} where the list begins.
       * @return an <code>Object[]</code> containing an <code>Integer</code> offset and the decoded
       *          list (as a {@code List}), in positions 0 and 1, respectively
       * @throws BencodingException if the bencoded object is incorrectly encoded.
       */
      @SuppressWarnings("unchecked")
  	private static final Object[] decodeList(byte[] bencoded_bytes, int offset) throws BencodingException
      {
          ArrayList list = new ArrayList();
          offset++;
          Object[] vals;
          while(bencoded_bytes[offset] != (byte)'e')
          {
              vals = decode(bencoded_bytes,offset);
              offset = ((Integer)vals[0]).intValue();
              list.add(vals[1]);
          }
          offset++;
          return new Object[] {new Integer(offset), list};
      }
      
      /**
       * Decodes a dictionary from the bencoded byte array.
       * @param bencoded_bytes the bencoded form of the dictionary.
       * @param offset the offset into {@code bencoded_bytes} where the dictionary begins.
       * @return an <code>Object[]</code> containing an <code>Integer</code> offset and the decoded
       *          dictionary (as a {@code Map}, in positions 0 and 1, respectively
       * @throws BencodingException if the bencoded object is incorrectly encoded.
       */
      @SuppressWarnings("unchecked")
  	private static final Object[] decodeDictionary(byte[] bencoded_bytes, int offset) throws BencodingException
      {
          HashMap map = new HashMap();
          ++offset;
          ByteBuffer info_hash_bytes = null;
          while(bencoded_bytes[offset] != (byte)'e')
          {
  
              // Decode the key, which must be a byte string
              Object[] vals = decodeString(bencoded_bytes, offset);
              ByteBuffer key = (ByteBuffer)vals[1];
              offset = ((Integer)vals[0]).intValue();
              boolean match = true;
              for(int i = 0; i < key.array().length && i < 4; i++)
              {
                  if(!key.equals(ByteBuffer.wrap(new byte[]{'i', 'n','f','o'})))
                  {
                      match = false;
                      break;
                  }
              }
              int info_offset = -1;
              if(match)
                  info_offset = offset;
              vals = decode(bencoded_bytes, offset);
              offset = ((Integer)vals[0]).intValue();
              if(match)
              {
                  info_hash_bytes = ByteBuffer.wrap(new byte[offset - info_offset]);
                  info_hash_bytes.put(bencoded_bytes,info_offset, info_hash_bytes.array().length);
              }
              else if(vals[1] instanceof HashMap)
              {
                  info_hash_bytes = (ByteBuffer)vals[2];
              }
              if(vals[1] != null)
                  map.put(key,vals[1]);
          }
  
          return new Object[] {new Integer(++offset), map, info_hash_bytes};
      }
      
      /**
       * Determines the bencoded data type at {@code bencoded_bytes[offset]}.
       * @param bencoded_bytes the bencoded data.
       * @param offset the offset into {@code bencoded_bytes} that contains a bencoded object.
       * @return the type of the bencoded object.
       * @see #DICTIONARY
       * @see #LIST
       * @see #INTEGER
       * @see #STRING
       * @see #INVALID
       */
      private static final int nextObject(byte[] bencoded_bytes, int offset)
      {
          switch(bencoded_bytes[offset])
          {
          case (byte)'d':
              return DICTIONARY;
          case (byte)'i':
              return INTEGER;
          case (byte)'l':
              return LIST;
          case (byte)'0':
          case (byte)'1':
          case (byte)'2':
          case (byte)'3':
          case (byte)'4':
          case (byte)'5':
          case (byte)'6':
          case (byte)'7':
          case (byte)'8':
          case (byte)'9':
              return STRING;
          default:
              return INVALID;
          }
      }
      
      /*
       ********************************************
       ************ BENCODING METHODS *************
       ********************************************
       */
      
      /**
       * Bencodes the specified object as a {@code byte[]}.
       * @param o the object to bencode.
       * @return the bencoded form of the object.
       * @throws BencodingException if {@code o} is not of type {@code HashMap}, {@code ArrayList},
       *  		{@code Integer}, or {@code ByteBuffer}.
       */
      @SuppressWarnings("unchecked")
  	public static final byte[] encode(Object o) throws BencodingException
      {
          if(o instanceof HashMap)
              return encodeDictionary((HashMap)o);
          else if(o instanceof ArrayList)
              return encodeList((ArrayList)o);
          else if(o instanceof Integer)
              return encodeInteger((Integer)o);
          else if(o instanceof ByteBuffer)
              return encodeString((ByteBuffer)o);
          else
              throw new BencodingException("Error: Object not of valid type for Bencoding.");
      }
      
      
      /**
       * Bencodes the specified byte string.
       * @param string the byte string to bencode.
       * @return a {@code byte[]} containing the bencoded form of the byte string.
       */
      private static final byte[] encodeString(ByteBuffer string)
      {
          int length = string.array().length;
          int num_digits = 1;
          while((length /= 10) > 0)
          {
              num_digits++;
          }
          byte[] bencoded_string = new byte[length+num_digits+1];
          bencoded_string[num_digits] = (byte)':';
          System.arraycopy(string.array(), 0, bencoded_string, num_digits+1, length);
          for(int i = num_digits-1; i >= 0; i--)
          {
              bencoded_string[i] = (byte)((length % 10)+48);
              length /= 10;
          }
          return bencoded_string;
      }
      
      /**
       * Bencodes the specified Integer.
       * @param integer the Integer to bencode.
       * @return a {@code byte[]} containing the bencoded form of the Integer.
       */
      private static final byte[] encodeInteger(Integer integer)
      {
          int num_digits = 1;
          int int_val = integer.intValue();
          while((int_val /= 10) > 0)
              ++num_digits;
          int_val = integer.intValue();
          byte[] bencoded_integer = new byte[num_digits+2];
          bencoded_integer[0] = (byte)'i';
          bencoded_integer[bencoded_integer.length - 1] = (byte)'e';
          for(int i = num_digits; i > 0; i--)
          {
              bencoded_integer[i] = (byte)((int_val % 10)+48);
              int_val /= 10;
          }
          return bencoded_integer;
      }
      
      /**
       * Bencodes the specified {@code ArrayList}.
       * @param list the {@code ArrayList} to bencode. 
       * @return a {@code byte[]} containing the bencoded form of the {@code ArrayList}.
       * @throws BencodingException if any of the objects in the list is not bencodable.
       */
      @SuppressWarnings("unchecked")
  	private static final byte[] encodeList(ArrayList list) throws BencodingException
      {
          byte[][] list_segments = new byte[list.size()][];
          for(int i = 0; i < list_segments.length;i++)
          {
              list_segments[i] = encode(list.get(i));
          }
          int total_length = 2;
          for(int i = 0 ; i < list_segments.length; i++)
              total_length += list_segments[i].length;
  