1/*
2 * Copyright (C) 2013 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.inputmethod.latin.utils;
18
19import com.android.inputmethod.annotations.UsedForTesting;
20
21import java.io.EOFException;
22import java.io.IOException;
23import java.io.LineNumberReader;
24
25@UsedForTesting
26public class Base64Reader {
27    private final LineNumberReader mReader;
28
29    private String mLine;
30    private int mCharPos;
31    private int mByteCount;
32
33    @UsedForTesting
34    public Base64Reader(final LineNumberReader reader) {
35        mReader = reader;
36        reset();
37    }
38
39    @UsedForTesting
40    public void reset() {
41        mLine = null;
42        mCharPos = 0;
43        mByteCount = 0;
44    }
45
46    @UsedForTesting
47    public int getLineNumber() {
48        return mReader.getLineNumber();
49    }
50
51    @UsedForTesting
52    public int getByteCount() {
53        return mByteCount;
54    }
55
56    private void fillBuffer() throws IOException {
57        if (mLine == null || mCharPos >= mLine.length()) {
58            mLine = mReader.readLine();
59            mCharPos = 0;
60        }
61        if (mLine == null) {
62            throw new EOFException();
63        }
64    }
65
66    private int peekUint8() throws IOException {
67        fillBuffer();
68        final char c = mLine.charAt(mCharPos);
69        if (c >= 'A' && c <= 'Z')
70            return c - 'A' + 0;
71        if (c >= 'a' && c <= 'z')
72            return c - 'a' + 26;
73        if (c >= '0' && c <= '9')
74            return c - '0' + 52;
75        if (c == '+')
76            return 62;
77        if (c == '/')
78            return 63;
79        if (c == '=')
80            return 0;
81        throw new RuntimeException("Unknown character '" + c + "' in base64 at line "
82                + mReader.getLineNumber());
83    }
84
85    private int getUint8() throws IOException {
86        final int value = peekUint8();
87        mCharPos++;
88        return value;
89    }
90
91    @UsedForTesting
92    public int readUint8() throws IOException {
93        final int value1, value2;
94        switch (mByteCount % 3) {
95        case 0:
96            value1 = getUint8() << 2;
97            value2 = value1 | (peekUint8() >> 4);
98            break;
99        case 1:
100            value1 = (getUint8() & 0x0f) << 4;
101            value2 = value1 | (peekUint8() >> 2);
102            break;
103        default:
104            value1 = (getUint8() & 0x03) << 6;
105            value2 = value1 | getUint8();
106            break;
107        }
108        mByteCount++;
109        return value2;
110    }
111
112    @UsedForTesting
113    public short readInt16() throws IOException {
114        final int data = readUint8() << 8;
115        return (short)(data | readUint8());
116    }
117}
118