Search This Blog

2013-05-01

Radix64Converter.java

Pre[-]
package gitzz.codec.binary;

/**
 * a radix-2 representation and radix-64 representation converter.
 * <p/>
 * This class is thread-safe.
 * 
 * @author sandynz
 */
public class Radix64Converter {

    /**
     * radix-64 representation allowed characters.
     * <p/>
     * sorted by ASCII ascending, so we can compare radix-64 representation which have the same length
     * by comparing ASCII.
     */
    private static final char[] ALLOWED_CHARS = "+-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
            .toCharArray();

    /**
     * convert radix-2 representation to radix-64 representation
     * 
     * @param r2
     *          radix-2 representation, i.e. 616
     * @return radix-64 representation, i.e. "7c"
     * @throws RuntimeException
     *           if there's internal error
     */
    public static String r2ToR64(final long r2) {
        final int distance = r2 != 0 ? 64 - Long.numberOfLeadingZeros(r2) : 1;
        final int r64CharsLen = distance / 6 + (distance % 6 != 0 ? 1 : 0);
        final char[] r64Chars = new char[r64CharsLen];
        long l = r2;
        int round = 0;
        do {
            r64Chars[r64CharsLen - ++round] = ALLOWED_CHARS[(int) (l & 0x3F)];
            l = l >>> 6;
        }
        while (l != 0);
        if (round != r64CharsLen)
            throw new RuntimeException(String.format("internal error : round != r64CharsLen, r2=%d", r2));
        return new String(r64Chars);
    }

    private static final String MAX_VALUE = r2ToR64(-1L);
    private static final int MAX_VALUE_LEN = MAX_VALUE.length();

    /** store represented value of ALLOWED_CHAR, it's index of ALLOWED_CHAR in the array */
    private static final byte[] ALLOWED_CHARS_VAL = new byte[128];
    static {
        for (byte i = 0; i < 64; i++) {
            ALLOWED_CHARS_VAL[ALLOWED_CHARS[i]] = i;
        }
    }

    /**
     * convert radix-64 representation to radix-2 representation
     * 
     * @param r64
     *          radix-64 representation, i.e. "7c"
     * @return radix-2 representation, i.e. 616
     * @throws IllegalArgumentException
     *           if radix-64 representation overflow or its format is wrong
     */
    public static long r64ToR2(final String r64) {
        final int r64Len = r64.length();
        // overflow detection must be done, otherwise, much string could be mapped to the same number,
        // if the number represent userId etc, then it may cause security vulnerability.
        if (r64Len > MAX_VALUE_LEN) {
            throw new IllegalArgumentException(String.format("radix-64 representation %s overflow", r64));
        }
        // radix-64 representation pattern, regex is "^(?:[A-Za-z0-9-][A-Za-z0-9+-]*)|(?:\+)$".
        // in fact, +7c is equal to 7c, since '+' represent 0, so radix-64 representation won't start
        // with '+', except the related radix-2 representation is 0.
        //
        // cache java.util.regex.Pattern static variable, use Matcher to match r64,
        // it's less efficient than manual coding, so it's replaced.
        boolean isR64Invalid = false;
        if (r64Len == 1) {
            char c = r64.charAt(0);
            isR64Invalid = !(c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z' || c >= '0' && c <= '9'
                    || c == '+' || c == '-');
        }
        else {
            char c = r64.charAt(0);
            if (c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z' || c >= '0' && c <= '9' || c == '-') {
                for (int i = 1; i < r64Len; i++) {
                    c = r64.charAt(i);
                    if ((c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z' || c >= '0' && c <= '9' || c == '+' || c == '-') == false) {
                        isR64Invalid = true;
                        break;
                    }
                }
                if (isR64Invalid == false && r64Len == MAX_VALUE_LEN && r64.compareTo(MAX_VALUE) > 0)
                    throw new IllegalArgumentException(String.format("radix-64 representation %s overflow",
                            r64));
            }
            else {
                isR64Invalid = true;
            }
        }
        if (isR64Invalid)
            throw new IllegalArgumentException(
                    String.format("radix-64 representation %s is invalid", r64));
        long r2 = 0;
        for (int i = 0; i < r64Len; i++) {
            r2 <<= 6;
            r2 |= ALLOWED_CHARS_VAL[r64.charAt(i)];
        }
        return r2;
    }

}

=Revisions=

201303

No comments:

Post a Comment