1/*
2 * Copyright (C) 2010 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 android.util;
18
19import java.io.FilterOutputStream;
20import java.io.IOException;
21import java.io.OutputStream;
22
23/**
24 * An OutputStream that does Base64 encoding on the data written to
25 * it, writing the resulting data to another OutputStream.
26 */
27public class Base64OutputStream extends FilterOutputStream {
28    private final Base64.Coder coder;
29    private final int flags;
30
31    private byte[] buffer = null;
32    private int bpos = 0;
33
34    private static byte[] EMPTY = new byte[0];
35
36    /**
37     * Performs Base64 encoding on the data written to the stream,
38     * writing the encoded data to another OutputStream.
39     *
40     * @param out the OutputStream to write the encoded data to
41     * @param flags bit flags for controlling the encoder; see the
42     *        constants in {@link Base64}
43     */
44    public Base64OutputStream(OutputStream out, int flags) {
45        this(out, flags, true);
46    }
47
48    /**
49     * Performs Base64 encoding or decoding on the data written to the
50     * stream, writing the encoded/decoded data to another
51     * OutputStream.
52     *
53     * @param out the OutputStream to write the encoded data to
54     * @param flags bit flags for controlling the encoder; see the
55     *        constants in {@link Base64}
56     * @param encode true to encode, false to decode
57     *
58     * @hide
59     */
60    public Base64OutputStream(OutputStream out, int flags, boolean encode) {
61        super(out);
62        this.flags = flags;
63        if (encode) {
64            coder = new Base64.Encoder(flags, null);
65        } else {
66            coder = new Base64.Decoder(flags, null);
67        }
68    }
69
70    public void write(int b) throws IOException {
71        // To avoid invoking the encoder/decoder routines for single
72        // bytes, we buffer up calls to write(int) in an internal
73        // byte array to transform them into writes of decently-sized
74        // arrays.
75
76        if (buffer == null) {
77            buffer = new byte[1024];
78        }
79        if (bpos >= buffer.length) {
80            // internal buffer full; write it out.
81            internalWrite(buffer, 0, bpos, false);
82            bpos = 0;
83        }
84        buffer[bpos++] = (byte) b;
85    }
86
87    /**
88     * Flush any buffered data from calls to write(int).  Needed
89     * before doing a write(byte[], int, int) or a close().
90     */
91    private void flushBuffer() throws IOException {
92        if (bpos > 0) {
93            internalWrite(buffer, 0, bpos, false);
94            bpos = 0;
95        }
96    }
97
98    public void write(byte[] b, int off, int len) throws IOException {
99        if (len <= 0) return;
100        flushBuffer();
101        internalWrite(b, off, len, false);
102    }
103
104    public void close() throws IOException {
105        IOException thrown = null;
106        try {
107            flushBuffer();
108            internalWrite(EMPTY, 0, 0, true);
109        } catch (IOException e) {
110            thrown = e;
111        }
112
113        try {
114            if ((flags & Base64.NO_CLOSE) == 0) {
115                out.close();
116            } else {
117                out.flush();
118            }
119        } catch (IOException e) {
120            if (thrown != null) {
121                thrown = e;
122            }
123        }
124
125        if (thrown != null) {
126            throw thrown;
127        }
128    }
129
130    /**
131     * Write the given bytes to the encoder/decoder.
132     *
133     * @param finish true if this is the last batch of input, to cause
134     *        encoder/decoder state to be finalized.
135     */
136    private void internalWrite(byte[] b, int off, int len, boolean finish) throws IOException {
137        coder.output = embiggen(coder.output, coder.maxOutputSize(len));
138        if (!coder.process(b, off, len, finish)) {
139            throw new Base64DataException("bad base-64");
140        }
141        out.write(coder.output, 0, coder.op);
142    }
143
144    /**
145     * If b.length is at least len, return b.  Otherwise return a new
146     * byte array of length len.
147     */
148    private byte[] embiggen(byte[] b, int len) {
149        if (b == null || b.length < len) {
150            return new byte[len];
151        } else {
152            return b;
153        }
154    }
155}
156