Base64 Codec : Base64 Stream « File Input Output « Java






Base64 Codec

    
/**
 * Created Aug 28, 2006
 */

/*
 Copyright 2007 Robert C. Ilardi

 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at

 http://www.apache.org/licenses/LICENSE-2.0

 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 limitations under the License.
 */


import java.io.ByteArrayOutputStream;
import java.util.HashMap;

/**
 * @author Robert C. Ilardi
 *
 */

public class Base64Codec {

  //NOTE: '=' is NOT a digit, it is a special terminator value. It has the index of 64 (the 65th element) for fast conversions...
  public static final char[] DIGITS = { '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', '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', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/', '=' };

  public static final HashMap<Character, Integer> DIGITS_MAP = new HashMap<Character, Integer>();

  static {
    //Init HashMap of Base 64 Digits to Base 10 Integers
    DIGITS_MAP.put('A', 0);
    DIGITS_MAP.put('B', 1);
    DIGITS_MAP.put('C', 2);
    DIGITS_MAP.put('D', 3);
    DIGITS_MAP.put('E', 4);
    DIGITS_MAP.put('F', 5);
    DIGITS_MAP.put('G', 6);
    DIGITS_MAP.put('H', 7);
    DIGITS_MAP.put('I', 8);
    DIGITS_MAP.put('J', 9);
    DIGITS_MAP.put('K', 10);
    DIGITS_MAP.put('L', 11);
    DIGITS_MAP.put('M', 12);
    DIGITS_MAP.put('N', 13);
    DIGITS_MAP.put('O', 14);
    DIGITS_MAP.put('P', 15);
    DIGITS_MAP.put('Q', 16);
    DIGITS_MAP.put('R', 17);
    DIGITS_MAP.put('S', 18);
    DIGITS_MAP.put('T', 19);
    DIGITS_MAP.put('U', 20);
    DIGITS_MAP.put('V', 21);
    DIGITS_MAP.put('W', 22);
    DIGITS_MAP.put('X', 23);
    DIGITS_MAP.put('Y', 24);
    DIGITS_MAP.put('Z', 25);
    DIGITS_MAP.put('a', 26);
    DIGITS_MAP.put('b', 27);
    DIGITS_MAP.put('c', 28);
    DIGITS_MAP.put('d', 29);
    DIGITS_MAP.put('e', 30);
    DIGITS_MAP.put('f', 31);
    DIGITS_MAP.put('g', 32);
    DIGITS_MAP.put('h', 33);
    DIGITS_MAP.put('i', 34);
    DIGITS_MAP.put('j', 35);
    DIGITS_MAP.put('k', 36);
    DIGITS_MAP.put('l', 37);
    DIGITS_MAP.put('m', 38);
    DIGITS_MAP.put('n', 39);
    DIGITS_MAP.put('o', 40);
    DIGITS_MAP.put('p', 41);
    DIGITS_MAP.put('q', 42);
    DIGITS_MAP.put('r', 43);
    DIGITS_MAP.put('s', 44);
    DIGITS_MAP.put('t', 45);
    DIGITS_MAP.put('u', 46);
    DIGITS_MAP.put('v', 47);
    DIGITS_MAP.put('w', 48);
    DIGITS_MAP.put('x', 49);
    DIGITS_MAP.put('y', 50);
    DIGITS_MAP.put('z', 51);
    DIGITS_MAP.put('0', 52);
    DIGITS_MAP.put('1', 53);
    DIGITS_MAP.put('2', 54);
    DIGITS_MAP.put('3', 55);
    DIGITS_MAP.put('4', 56);
    DIGITS_MAP.put('5', 57);
    DIGITS_MAP.put('6', 58);
    DIGITS_MAP.put('7', 59);
    DIGITS_MAP.put('8', 60);
    DIGITS_MAP.put('9', 61);
    DIGITS_MAP.put('+', 62);
    DIGITS_MAP.put('/', 63);
    DIGITS_MAP.put('=', -1);
  }

  public static final int PADDING_INDEX = 64;

  //Start Encoding Code Block--------------------------------------------------------------------------->

  public static String Encode(byte[] data) {
    return Encode(data, true);
  }

  /**
   * 
   * Base 64 Encoding works off of a 24 bit buffer.
   * It basically converts 3 bytes into 4, since normal
   * ASCII binary data is really base 256, we are stepping down
   * to base 64, therefore the number of "digits" for a single
   * number increases.
   * 
   */
  public static String Encode(byte[] data, boolean newlines) {
    StringBuffer b64Data = new StringBuffer();
    int ascii, buf, padding, cnt;
    int[] b64Set;
    String b64Num;

    if (data != null) {
      cnt = 0; //MUST be set to 0 NOT 1 because the first iteration of the for i loop executes cnt++ before appending to the string buffer
      for (int i = 0; i < data.length; i += 3) {
        buf = 0;
        padding = 2;

        //First byte (will eventually become left most 8 bits in the 24-bit buffer)
        ascii = data[i] & 0xFF;
        buf = ascii;
        buf = buf << 8;

        //Second byte (will eventually become middle 8 bits in the 24-bit buffer) 
        if ((i + 1) < data.length) {
          ascii = data[i + 1] & 0xFF;
          buf = buf + ascii;
          padding--;
        }
        buf = buf << 8;

        //Third byte (will eventually become the right most 8 bits in the 24-bit buffer)
        if ((i + 2) < data.length) {
          ascii = data[i + 2] & 0xFF;
          buf = buf + ascii;
          padding--;
        }

        b64Set = ConvertToBase64Set(buf, padding); //Obtain Digit Indexes 0-63 normally, with a special 64th index for terminator value
        b64Num = ConvertToBase64Number(b64Set); //Get actual string reprentation of the 3 bytes as a base 64 number (4 bytes).

        //RFC 1421 and RFC 2045 requires a CR+LF newline every 76 characters OR ever 19 sets of 4 bytes base 64 numbers.
        if (cnt == 19) {
          cnt = 1; //Because the first iteration of the for i loop the ELSE block is executed instead, we set to 1 instead of 0.
          if (newlines) {
            b64Data.append("\r\n"); //CR+LF
          }
        }
        else {
          cnt++;
        }

        b64Data.append(b64Num);
      } //End for i loop through data byte array

      //Trailing CR+LF newline if required
      if (cnt == 19 && b64Data.charAt(b64Data.length() - 1) != '=') {
        b64Data.append("\r\n"); //CR+LF
      }

    } //End data != null check

    return b64Data.toString();
  }

  /**
   * Does the actual base 64 conversion math 
   */
  private static int[] ConvertToBase64Set(int b10Num, int padding) {
    int[] b64Set = new int[4];
    int dividend, quotient, remainder, cnt;
    final int divisor = 64;

    cnt = 3;
    dividend = b10Num;

    do {
      quotient = dividend / divisor;
      remainder = dividend % divisor;

      dividend = quotient;

      b64Set[cnt] = remainder;
      cnt--;
    } while (quotient > 0);

    for (int i = 0; i < padding; i++) {
      b64Set[3 - i] = PADDING_INDEX;
    }

    return b64Set;
  }

  /**
   * Simply does a table lookup for the correct digits
   */
  private static String ConvertToBase64Number(int[] b64Set) {
    StringBuffer b64Num = new StringBuffer();

    for (int i = 0; i < b64Set.length; i++) {
      b64Num.append(DIGITS[b64Set[i]]);
    }

    return b64Num.toString();
  }

  //Start Decoding Code Block--------------------------------------------------------------------------->

  public static byte[] Decode(String b64Data) {
    return Decode(b64Data, true);
  }

  public static byte[] Decode(String b64Data, boolean newlines) {
    ByteArrayOutputStream baos = null;
    byte[] data = null, tmp;
    int b64Val, buf, b1, b2, b3, cnt, padding;

    if (b64Data != null && b64Data.length() > 0) {
      cnt = 0;
      tmp = new byte[3];
      baos = new ByteArrayOutputStream();

      for (int i = 0; i < b64Data.length(); i += 4) {
        buf = 0;
        padding = 0;

        b64Val = DIGITS_MAP.get(b64Data.charAt(i));
        buf = b64Val * ((int) Math.pow(64, 3));

        b64Val = DIGITS_MAP.get(b64Data.charAt(i + 1));
        buf += b64Val * ((int) Math.pow(64, 2));

        b64Val = DIGITS_MAP.get(b64Data.charAt(i + 2));
        if (b64Val != -1) {
          buf += b64Val * ((int) Math.pow(64, 1));

          b64Val = DIGITS_MAP.get(b64Data.charAt(i + 3));
          if (b64Val != -1) {
            buf += b64Val; //same as b64Val * ((int) Math.pow(64, 0))
          } //End second -1 check
          else {
            padding = 1;
          }
        } //End first -1 check
        else {
          padding = 2;
        }

        //First Byte
        b1 = buf >> 16;
        tmp[0] = (byte) b1;

        //Second Byte
        if (padding != 2) {
          b2 = buf - (b1 << 16);
          b2 = b2 >> 8;
          tmp[1] = (byte) b2;

          //Third Byte
          if (padding == 0) {
            b3 = buf - ((b1 << 16) + (b2 << 8));
            tmp[2] = (byte) b3;
          } //End padding == 0 check
        } //End padding != 2 check

        baos.write(tmp, 0, 3 - padding);

        //RFC 1421 and RFC 2045 requires a CR+LF newline every 76 characters base 64 numbers.
        cnt += 4;

        if (cnt == 76) {
          cnt = 0;

          if (newlines) {
            i += 2; //Advance i to skip the CR+LF newline
          }
        }
      } //End for i loop through b64Data String

      data = baos.toByteArray();
    } //End b64Data null and length check

    return data;
  }

}

   
    
    
    
  








Related examples in the same category

1.BASE64 Decoder Stream
2.BASE64 Encoder Stream from Sun Microsystems
3.Base64 Character decoder as specified in RFC1113.
4.Base64 Character encoder as specified in RFC1113.
5.Performs Base-64 decoding on an underlying stream.
6.Class encodes the bytes written to the OutPutStream to a Base64 encoded string.
7.BASE64 Decoder Stream from Sun Microsystems
8.BASE64 Encoder Stream
9.Decode a BASE64 encoded input stream to some output stream
10.Hex dump
11.Dumps data in hexadecimal format
12.Apply a ASCII Hex encoding to the stream
13.Base64 encoding from DbUnit.org
14.Base64 provides Base64 encoding/decoding of strings and streams
15.Base64 - encode/decode data using the Base64 encoding scheme
16.Base64 from Eric Glass jcifs at samba dot org