Merge branch 'subtree'
[fanfix.git] / streams / Base64OutputStream.java
CommitLineData
f28a134e
NR
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 be.nikiroo.utils.streams;
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 */
42 public Base64OutputStream(OutputStream out) {
43 this(out, true);
44 }
45
46 /**
47 * Performs Base64 encoding or decoding on the data written to the
48 * stream, writing the encoded/decoded data to another
49 * OutputStream.
50 *
51 * @param out the OutputStream to write the encoded data to
52 * @param encode true to encode, false to decode
53 *
54 * @hide
55 */
56 public Base64OutputStream(OutputStream out, boolean encode) {
57 super(out);
58 this.flags = Base64.NO_WRAP;
59 if (encode) {
60 coder = new Base64.Encoder(flags, null);
61 } else {
62 coder = new Base64.Decoder(flags, null);
63 }
64 }
65
a6a73de3
NR
66 @Override
67 public void write(int b) throws IOException {
f28a134e
NR
68 // To avoid invoking the encoder/decoder routines for single
69 // bytes, we buffer up calls to write(int) in an internal
70 // byte array to transform them into writes of decently-sized
71 // arrays.
72
73 if (buffer == null) {
74 buffer = new byte[1024];
75 }
76 if (bpos >= buffer.length) {
77 // internal buffer full; write it out.
78 internalWrite(buffer, 0, bpos, false);
79 bpos = 0;
80 }
81 buffer[bpos++] = (byte) b;
82 }
83
84 /**
85 * Flush any buffered data from calls to write(int). Needed
86 * before doing a write(byte[], int, int) or a close().
87 */
88 private void flushBuffer() throws IOException {
89 if (bpos > 0) {
90 internalWrite(buffer, 0, bpos, false);
91 bpos = 0;
92 }
93 }
94
a6a73de3
NR
95 @Override
96 public void write(byte[] b, int off, int len) throws IOException {
f28a134e
NR
97 if (len <= 0) return;
98 flushBuffer();
99 internalWrite(b, off, len, false);
100 }
101
a6a73de3
NR
102 @Override
103 public void close() throws IOException {
f28a134e
NR
104 IOException thrown = null;
105 try {
106 flushBuffer();
107 internalWrite(EMPTY, 0, 0, true);
108 } catch (IOException e) {
109 thrown = e;
110 }
111
112 try {
113 if ((flags & Base64.NO_CLOSE) == 0) {
114 out.close();
115 } else {
116 out.flush();
117 }
118 } catch (IOException e) {
119 if (thrown != null) {
120 thrown = e;
121 }
122 }
123
124 if (thrown != null) {
125 throw thrown;
126 }
127 }
128
129 /**
130 * Write the given bytes to the encoder/decoder.
131 *
132 * @param finish true if this is the last batch of input, to cause
133 * encoder/decoder state to be finalized.
134 */
135 private void internalWrite(byte[] b, int off, int len, boolean finish) throws IOException {
136 coder.output = embiggen(coder.output, coder.maxOutputSize(len));
137 if (!coder.process(b, off, len, finish)) {
138 throw new IOException("bad base-64");
139 }
140 out.write(coder.output, 0, coder.op);
141 }
142
143 /**
144 * If b.length is at least len, return b. Otherwise return a new
145 * byte array of length len.
146 */
147 private byte[] embiggen(byte[] b, int len) {
148 if (b == null || b.length < len) {
149 return new byte[len];
f28a134e 150 }
a6a73de3 151 return b;
f28a134e
NR
152 }
153}