All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.googlecode.jinahya.rfc4648.Base Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2011 Jin Kwon.
 *
 * 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.
 */


package com.googlecode.jinahya.rfc4648;


import com.googlecode.jinahya.io.BitInput;
import com.googlecode.jinahya.io.BitInput.StreamInput;
import com.googlecode.jinahya.io.BitOutput;
import com.googlecode.jinahya.io.BitOutput.StreamOutput;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.Writer;


/**
 * Abstract Base class.
 *
 * @author Jin Kwon
 */
public abstract class Base {


    /**
     * Default pad character.
     */
    static final char PAD = '=';


    /**
     * MAGIC NUMBER: OCTET SIZE.
     */
    private static final int OCTET_SIZE = 8;


    /**
     * MAGIC NUMBER: ASCII SIZE.
     */
    private static final int ASCII_SIZE = 128;


    /**
     * MAGIC NUMBER: SMALLEST VISIBLE ASCII.
     */
    private static final int SMALLEST_VISIBLE_ASCII = 0x21; // 33


    /**
     * Returns the Least Common Multiple value for given two operands.
     *
     * @param a the first operand
     * @param b the second operand
     *
     * @return calculated least common multiple
     */
    private static int lcm(final int a, final int b) {

        if (a < 0) {
            throw new IllegalArgumentException("a(" + a + ") < 0");
        }

        if (b < 0) {
            throw new IllegalArgumentException("b(" + b + ") < 0");
        }

        return (a / gcd(a, b)) * b;
    }


    /**
     * Returns the Greatest Common Divisor for given two operands.
     *
     * @param a the first operand
     * @param b the second operand
     *
     * @return calculated greatest common devisor
     */
    private static int gcd(final int a, final int b) {

        if (a < 0) {
            throw new IllegalArgumentException("a(" + a + ") < 0");
        }

        if (b < 0) {
            throw new IllegalArgumentException("b(" + b + ") < 0");
        }

        if (a < b) {
            return gcd(b, a);
        }

        if (b == 0) {
            return a;
        }

        return gcd(b, a % b);
    }


    /**
     * Create a new instance.
     *
     * @param alphabet alphabet to be used
     * @param padding flag for padding
     */
    protected Base(final byte[] alphabet, final boolean padding) {

        super();

        if (alphabet == null) {
            throw new NullPointerException("alphabet");
        }

        if (alphabet.length == 0) {
            throw new IllegalArgumentException("empty alphabet");
        }

        encode = alphabet;

        decode = new byte[ASCII_SIZE - SMALLEST_VISIBLE_ASCII + 1];
        for (int i = 0; i < decode.length; i++) {
            decode[i] = -1;
        }
        boolean lower_ = false;
        for (byte i = 0; i < encode.length; i++) {
            decode[encode[i] - SMALLEST_VISIBLE_ASCII] = i;
            if (encode[i] >= 0x61 && encode[i] <= 0x7A) { // 'a' <= c <= 'z'
                lower_ = true;
            }
        }
        this.lower = lower_;

        this.padding = padding;

        bitsPerChar = (int) (Math.log(encode.length) / Math.log(2.0d));
        bytesPerWord = lcm(OCTET_SIZE, bitsPerChar) / OCTET_SIZE;
        charsPerWord = bytesPerWord * OCTET_SIZE / bitsPerChar;
    }


    /**
     * Encodes bits from {@code input} and write those encoded characters to
     * {@code output}.
     *
     * @param input binary input
     * @param output character output
     *
     * @throws IOException if an I/O error occurs
     */
    private void encode(final BitInput input, final Writer output)
        throws IOException {

        if (input == null) {
            throw new IllegalArgumentException("input");
        }

        if (output == null) {
            throw new IllegalArgumentException("output");
        }

        outer:
        while (true) {
            for (int i = 0; i < charsPerWord; i++) {
                final int available =
                    OCTET_SIZE - ((bitsPerChar * i) % OCTET_SIZE);
                if (available >= bitsPerChar) {
                    try {
                        final int unsigned = input.readUnsignedInt(bitsPerChar);
                        output.write(encode[unsigned]);
                    } catch (EOFException eofe) { // i == 0
                        break outer;
                    }
                } else { // need next octet
                    final int required = bitsPerChar - available;
                    int unsigned =
                        (input.readUnsignedInt(available) << required);
                    try {
                        unsigned |= input.readUnsignedInt(required);
                        output.write(encode[unsigned]);
                    } catch (EOFException eofe) {
                        output.write(encode[unsigned]);
                        if (padding) {
                            for (int j = i + 1; j < charsPerWord; j++) {
                                output.write(PAD);
                            }
                        }
                        break outer;
                    }
                }
            }
        }
    }


    /**
     * Encodes bytes from given {@code input} and writes those encoded
     * characters to {@code output}.
     *
     * @param input binary input
     * @param output character output
     *
     * @throws IOException if an I/O error occurs
     */
    public void encode(final InputStream input, final Writer output)
        throws IOException {

        if (input == null) {
            throw new NullPointerException("input");
        }

        if (output == null) {
            throw new NullPointerException("output");
        }

        encode(new BitInput(new StreamInput(input)), output);
    }


    /**
     * Encodes bytes from {@code input} and writes those encoded octets to
     * {@code output}.
     *
     * @param input input
     * @param output output
     *
     * @throws IOException if an I/O error occurs.
     */
    public void encode(final InputStream input, final OutputStream output)
        throws IOException {

        if (input == null) {
            throw new NullPointerException("input");
        }

        if (output == null) {
            throw new NullPointerException("output");
        }

        final Writer writer = new OutputStreamWriter(output, "US-ASCII");
        encode(input, writer);
        writer.flush();
    }


    /**
     * Encodes bytes from given input stream and returns result.
     *
     * @param input the input stream
     *
     * @return encoded output
     *
     * @throws IOException if an I/O error occurs.
     */
    public final byte[] encode(final InputStream input) throws IOException {

        if (input == null) {
            throw new NullPointerException("input");
        }

        final ByteArrayOutputStream output = new ByteArrayOutputStream();

        encode(input, output);
        output.flush();

        return output.toByteArray();
    }


    /**
     * Encodes bytes in {@code input} and returns encoded characters.
     *
     * @param input byte input
     *
     * @return encoded characters
     *
     * @throws IOException if an I/O error occurs.
     */
    public byte[] encode(final byte[] input) throws IOException {

        if (input == null) {
            throw new NullPointerException("input");
        }

        return encode(new ByteArrayInputStream(input));
    }


    public String encodeToString(final byte[] input, final String outputCharset)
        throws IOException {

        if (input == null) {
            throw new NullPointerException("input");
        }

        if (outputCharset == null) {
            throw new NullPointerException("outputCharset");
        }

        return new String(encode(input), outputCharset);
    }


    public byte[] encode(final String input, final String inputCharset)
        throws IOException {

        if (input == null) {
            throw new NullPointerException("input");
        }

        if (inputCharset == null) {
            throw new NullPointerException("inputCharset");
        }

        return encode(input.getBytes(inputCharset));
    }


    public String encodeToString(final String input, final String inputCharset,
                                 final String outputCharset)
        throws IOException {

        if (outputCharset == null) {
            throw new NullPointerException("outputCharset");
        }

        return new String(encode(input, inputCharset), outputCharset);
    }


    /**
     * Decodes characters from {@code input} and writes decoded binary to
     * {@code output}.
     *
     * @param input character input
     * @param output binary output
     *
     * @throws IOException if an I/O error occurs
     */
    private void decode(final Reader input, final BitOutput output)
        throws IOException {

        if (input == null) {
            throw new NullPointerException("input");
        }

        if (output == null) {
            throw new NullPointerException("output");
        }

        outer:
        while (true) {
            int c;
            for (int i = 0; i < charsPerWord; i++) {
                c = input.read();
                if (c == -1) { // end of stream
                    if (i == 0) { // first character in a word; ok
                        break outer;
                    }
                    if (((i * bitsPerChar) % OCTET_SIZE) >= bitsPerChar) {
                        throw new EOFException("not finished properly");
                    }
                    if (!padding) {
                        break outer;
                    }
                    throw new EOFException("not finished properly");
                } else if (c == PAD) {
                    if (!padding) {
                        throw new IOException("bad padding; no pads allowed");
                    }
                    if (i == 0) { // first character in a word
                        throw new IOException("bad padding");
                    }
                    if (((i * bitsPerChar) % OCTET_SIZE) >= bitsPerChar) {
                        throw new IOException("bad padding");
                    }
                    for (int j = i + 1; j < charsPerWord; j++) {
                        c = input.read(); // pad
                        if (c == -1) { // end of stream?
                            throw new EOFException("not finished properly");
                        }
                        if (c != PAD) { // not the pad char?
                            throw new IOException("bad padding");
                        }
                    }
                    break outer;
                } else {
                    if (!lower && (c >= 0x61 && c <= 0x7A)) { // 'a' <= c <= 'z'
                        c -= 0x20; // to upper
                    }
                    final int value = decode[c - SMALLEST_VISIBLE_ASCII];
                    if (value == -1) {
                        throw new IOException("bad character: " + (char) c);
                    }
                    output.writeUnsignedInt(bitsPerChar, value);
                }
            }
        }
    }


    /**
     * Decodes characters from {@code input} and writes decoded bytes to
     * {@code output}.
     *
     * @param input character input
     * @param output binary output
     *
     * @throws IOException if an I/O error occurs
     */
    public void decode(final Reader input, final OutputStream output)
        throws IOException {

        if (input == null) {
            throw new NullPointerException("input");
        }

        if (output == null) {
            throw new NullPointerException("outpute");
        }

        decode(input, new BitOutput(new StreamOutput(output)));
    }


    /**
     * Decodes characters from {@code input} and writes decoded octets to
     * {@code output}.
     *
     * @param input input
     * @param output output
     *
     * @throws IOException if an I/O error occurs.
     */
    public void decode(final InputStream input, final OutputStream output)
        throws IOException {

        if (input == null) {
            throw new NullPointerException("input");
        }

        if (output == null) {
            throw new NullPointerException("outpute");
        }

        final Reader reader = new InputStreamReader(input, "US-ASCII");

        decode(reader, output);
    }


    /**
     * Decodes characters from {@code input} and return decoded bytes.
     *
     * @param input character input
     *
     * @return decoded bytes
     *
     * @throws IOException if an I/O error occurs.
     */
    public byte[] decode(final InputStream input) throws IOException {

        if (input == null) {
            throw new NullPointerException("input");
        }

        final ByteArrayOutputStream output = new ByteArrayOutputStream();

        decode(input, output);
        output.flush();

        return output.toByteArray();
    }


    /**
     * Decodes characters in {@code input} and return decoded bytes.
     *
     * @param input character input
     *
     * @return decoded bytes
     *
     * @throws IOException if an I/O error occurs.
     */
    public byte[] decode(final byte[] input) throws IOException {

        if (input == null) {
            throw new NullPointerException("input");
        }

        return decode(new ByteArrayInputStream(input));
    }


    public String decodeToString(final byte[] input, final String outputCharset)
        throws IOException {

        if (input == null) {
            throw new NullPointerException("input");
        }

        if (outputCharset == null) {
            throw new NullPointerException("outputCharset");
        }

        return new String(decode(input), outputCharset);
    }


    public byte[] decode(final String input, final String inputCharset)
        throws IOException {

        if (input == null) {
            throw new NullPointerException("input");
        }

        if (inputCharset == null) {
            throw new NullPointerException("inputCharset");
        }

        return decode(input.getBytes(inputCharset));
    }


    /**
     * Decodes given encoded input string.
     *
     * @param input the encoded input string.
     * @param inputCharset the charset name to decode input string.
     * @param outputCharset the charset name to encode output string.
     *
     * @return a decoded output as a string
     *
     * @throws IOException if an I/O error occurs.
     */
    public String decodeToString(final String input,
                                 final String inputCharset,
                                 final String outputCharset)
        throws IOException {

        if (outputCharset == null) {
            throw new NullPointerException("outputCharset");
        }

        return new String(decode(input, inputCharset), outputCharset);
    }


    /**
     * characters for encoding.
     */
    private final byte[] encode;


    /**
     * characters for decoding.
     */
    private final byte[] decode;


    /**
     * flag for lower-case characters.
     */
    private final boolean lower;


    /**
     * flag for padding.
     */
    private final boolean padding;


    /**
     * number of bits per character.
     */
    private final int bitsPerChar;


    /**
     * number of bytes per word.
     */
    private final int bytesPerWord;


    /**
     * number of characters per word.
     */
    private final int charsPerWord;


}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy