/** Klasa pomocnicza do konwersji w obu kierunkach między tekstem a Base64 */
 
public class Base64
{
   
/** Znaki reprezentujące każdy 6-bitowy wzorzec w Base 64 */
    public static final String base64Chars =
        "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

/** Konwersja tablicy bajtowej do ciągu znaków Base 64. */
    public static String toBase64(byte[] bytes)
    {
        StringBuffer outBuff = new StringBuffer();

// Pętla przebiegająca przez ciąg znaków i pobierająca 3 w każdym kroku
        for (int i=0; i < bytes.length; i += 3)
        {
// Przechowanie 3 8-bitowych znaków, które zostaną konwertowane do postaci 4 znaków 6-bitowych
            int packed64 = 0;

// Zakładamy, że każdorazowo wyprowadzane są 4 pełne znaki. Na końcu
// ciągu brakujące znaki zostaną wypełnione symbolem =
            int num64Chars = 4;
            
// Pobranie pierwszego znaku i umieszczenie w pakującej zmiennej całkowitoliczbowej
            packed64 = bytes[i] << 16;

// Dodanie do bufora innego znaku, jeżeli zostanie odnaleziony
            if (i+1 < bytes.length)
            {
                packed64 = packed64 + (bytes[i+1] << 8);
            }
            else
            {
// W przeciwnym przypadku wyprowadzone zostaną 2 znaki wypełniające. Spośród 4 znaków 6-bitowych,
// do przedstawienia liczby ośmiobitowej niezbędne są dwa.
                num64Chars = 2;
            }

// Dodanie do bufora kolejnego znaku, o ile występuje.
            if (i + 2 < bytes.length)
            {
                packed64 = packed64 + bytes[i+2];
            }
// W przeciwnym przypadku, jeżeli oczekiwano wyprowadzenia 4 znaków 6-bitowych,
// wyprowadzone zostały jedynie 3. Do tego punktu można dotrzeć
// po ustawieniu liczby znaków na 2, więc należy się upewnić, że przypadkowo
// nie zostanie zapisanych więcej znaków niż potrzeba.
            else if (num64Chars == 4)
            {
                num64Chars = 3;
            }

// Pętla przebiegająca przez zmienną pośredniczącą i pobierająca 6 bits jednocześnie. Wyprowadzanie
// znaku w formie ciągu base64Chars odpowiadającego każdej wartości
// 6-bitowej.
            for (int j=0; j < num64Chars; j++)
            {
                outBuff.append(base64Chars.charAt(
                    (packed64 >> (6 * (3-j))) & 63));
            }

// Jeżeli zapisano mniej niż 4 wartości 6-bitowe, wyprowadzane są znaki wypełniające
            for (int j=num64Chars; j < 4; j++)
            {
                outBuff.append('=');
            }
        }

// Zwrot ciągu Base64
        return outBuff.toString();
    }

/** Konwersja ciągu base64 do postaci zwykłego ciągu znaków */
    public static String fromBase64(String str)
    {

// TWorzenie bufora do przechowania zwykłego ciągu znaków.
        StringBuffer outBuff = new StringBuffer();

// Konwersja ciągu znaków base64 do postaci tablicy bajtowej
        byte[] stringBytes = str.getBytes();

// Pętla analizująca grupę bajtów w porcjach po 4.
        for (int i=0; i < stringBytes.length; i += 4)
        {
            int currWord = 0;

// Założenie, że ten zbiór bajtów zawiera 3 pełne znaki.
            int charsInWord = 3;

            for (int j=0; j < 4; j++)
            {
                byte ch = stringBytes[i+j];

// Natrafienie na znak wypełnienia oznacza, że w tym zbiorze 4 bajtów zakodowanych jest mniej
// niż 3 znaki. Jak się okaże, liczba zakodowanych znaków
// odpowiada pozycji, gdzie po raz pierwszy wystąpił znak '='
                if (ch == '=')
                {
                    charsInWord = j;
                    break;
                }

// Pobranie wartości 6-bitowej odpowiadającej temu znakowi.
                ch = (byte) base64Chars.indexOf(ch);

// Dodanie wartości 6-bitowych do zmiennej pośredniczącej (wartość 24-bitowa tworzona jest
// z 4 wartości 6-bitowych)
                currWord = currWord + (ch << (6 * (3 - j)));
            }

// Pobieranie znaków z pośredniczącej zmiennej całkowitoliczbowej w porcjach 
// po 8 bitów.
            for (int j=0; j < charsInWord; j++)
            {
                outBuff.append((char) ((currWord >> (8 * (2-j))) & 255));
            }
        }

// Zwrot odkodowanego ciągu znaków.
        return outBuff.toString();
    }

/** Program testowy sprawdzający poprawność działania procedury kodującej. Koduje
    każdy argument z wiersza poleceń do postaci base64 i z powrotem.
*/
    public static void main(String args[])
    {
        for (int i=0; i < args.length; i++)
        {
            String base64 = toBase64(args[i].getBytes());
            String un64 = fromBase64(base64);

            System.out.println(args[i]+" -> "+base64+" -> "+un64);
        }
    }
}
