Merge "add Base64InputStream"
This commit is contained in:
commit
092198f554
169
common/java/com/android/common/Base64InputStream.java
Normal file
169
common/java/com/android/common/Base64InputStream.java
Normal file
@ -0,0 +1,169 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* 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.android.common;
|
||||
|
||||
import java.io.FilterInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* An OutputStream that does either Base64 encoding or decoding on the
|
||||
* data written to it, writing the resulting data to another
|
||||
* OutputStream.
|
||||
*/
|
||||
public class Base64InputStream extends FilterInputStream {
|
||||
private final boolean encode;
|
||||
private final Base64.EncoderState estate;
|
||||
private final Base64.DecoderState dstate;
|
||||
|
||||
private static byte[] EMPTY = new byte[0];
|
||||
|
||||
private static final int BUFFER_SIZE = 2048;
|
||||
private boolean eof;
|
||||
private byte[] inputBuffer;
|
||||
private byte[] outputBuffer;
|
||||
private int outputStart;
|
||||
private int outputEnd;
|
||||
|
||||
/**
|
||||
* An InputStream that performs Base64 decoding on the data read
|
||||
* from the wrapped stream.
|
||||
*
|
||||
* @param in the InputStream to read the source data from
|
||||
* @param flags bit flags for controlling the decoder; see the
|
||||
* constants in {@link Base64}
|
||||
*/
|
||||
public Base64InputStream(InputStream out, int flags) {
|
||||
this(out, flags, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs Base64 encoding or decoding on the data read from the
|
||||
* wrapped InputStream.
|
||||
*
|
||||
* @param in the InputStream to read the source data from
|
||||
* @param flags bit flags for controlling the decoder; see the
|
||||
* constants in {@link Base64}
|
||||
* @param encode true to encode, false to decode
|
||||
*/
|
||||
public Base64InputStream(InputStream out, int flags, boolean encode) {
|
||||
super(out);
|
||||
this.encode = encode;
|
||||
eof = false;
|
||||
inputBuffer = new byte[BUFFER_SIZE];
|
||||
if (encode) {
|
||||
// len*8/5+10 is an overestimate of the most bytes the
|
||||
// encoder can produce for len bytes of input.
|
||||
outputBuffer = new byte[BUFFER_SIZE * 8/5 + 10];
|
||||
estate = new Base64.EncoderState(flags, outputBuffer);
|
||||
dstate = null;
|
||||
} else {
|
||||
// len*3/4+10 is an overestimate of the most bytes the
|
||||
// decoder can produce for len bytes of input.
|
||||
outputBuffer = new byte[BUFFER_SIZE * 3/4 + 10];
|
||||
estate = null;
|
||||
dstate = new Base64.DecoderState(flags, outputBuffer);
|
||||
}
|
||||
outputStart = 0;
|
||||
outputEnd = 0;
|
||||
}
|
||||
|
||||
public boolean markSupported() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public void mark(int readlimit) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public void close() throws IOException {
|
||||
in.close();
|
||||
inputBuffer = null;
|
||||
}
|
||||
|
||||
public int available() {
|
||||
return outputEnd - outputStart;
|
||||
}
|
||||
|
||||
public long skip(long n) throws IOException {
|
||||
if (outputStart >= outputEnd) {
|
||||
refill();
|
||||
}
|
||||
if (outputStart >= outputEnd) {
|
||||
return 0;
|
||||
}
|
||||
long bytes = Math.min(n, outputEnd-outputStart);
|
||||
outputStart += bytes;
|
||||
return bytes;
|
||||
}
|
||||
|
||||
public int read() throws IOException {
|
||||
if (outputStart >= outputEnd) {
|
||||
refill();
|
||||
}
|
||||
if (outputStart >= outputEnd) {
|
||||
return -1;
|
||||
} else {
|
||||
return outputBuffer[outputStart++];
|
||||
}
|
||||
}
|
||||
|
||||
public int read(byte[] b, int off, int len) throws IOException {
|
||||
if (outputStart >= outputEnd) {
|
||||
refill();
|
||||
}
|
||||
if (outputStart >= outputEnd) {
|
||||
return -1;
|
||||
}
|
||||
int bytes = Math.min(len, outputEnd-outputStart);
|
||||
System.arraycopy(outputBuffer, outputStart, b, off, bytes);
|
||||
outputStart += bytes;
|
||||
return bytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read data from the input stream into inputBuffer, then
|
||||
* decode/encode it into the empty outputBuffer, and reset the
|
||||
* outputStart and outputEnd pointers.
|
||||
*/
|
||||
private void refill() throws IOException {
|
||||
if (eof) return;
|
||||
int bytesRead = in.read(inputBuffer);
|
||||
if (encode) {
|
||||
if (bytesRead == -1) {
|
||||
eof = true;
|
||||
Base64.encodeInternal(EMPTY, 0, 0, estate, true);
|
||||
} else {
|
||||
Base64.encodeInternal(inputBuffer, 0, bytesRead, estate, false);
|
||||
}
|
||||
outputEnd = estate.op;
|
||||
} else {
|
||||
if (bytesRead == -1) {
|
||||
eof = true;
|
||||
Base64.decodeInternal(EMPTY, 0, 0, dstate, true);
|
||||
} else {
|
||||
Base64.decodeInternal(inputBuffer, 0, bytesRead, dstate, false);
|
||||
}
|
||||
outputEnd = dstate.op;
|
||||
}
|
||||
outputStart = 0;
|
||||
}
|
||||
}
|
@ -18,6 +18,7 @@ package com.android.common;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.util.Random;
|
||||
|
||||
@ -228,8 +229,13 @@ public class Base64Test extends TestCase {
|
||||
/**
|
||||
* Tests that Base64.encodeInternal does correct handling of the
|
||||
* tail for each call.
|
||||
*
|
||||
* This test is disabled because while it passes if you can get it
|
||||
* to run, android's test infrastructure currently doesn't allow
|
||||
* us to get at package-private members (Base64.EncoderState in
|
||||
* this case).
|
||||
*/
|
||||
public void testEncodeInternal() throws Exception {
|
||||
public void XXXtestEncodeInternal() throws Exception {
|
||||
byte[] input = { (byte) 0x61, (byte) 0x62, (byte) 0x63 };
|
||||
byte[] output = new byte[100];
|
||||
|
||||
@ -272,6 +278,132 @@ public class Base64Test extends TestCase {
|
||||
assertEquals("YQ".getBytes(), 2, state.output, state.op);
|
||||
}
|
||||
|
||||
private static final String lipsum =
|
||||
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. " +
|
||||
"Quisque congue eleifend odio, eu ornare nulla facilisis eget. " +
|
||||
"Integer eget elit diam, sit amet laoreet nibh. Quisque enim " +
|
||||
"urna, pharetra vitae consequat eget, adipiscing eu ante. " +
|
||||
"Aliquam venenatis arcu nec nibh imperdiet tempor. In id dui " +
|
||||
"eget lorem aliquam rutrum vel vitae eros. In placerat ornare " +
|
||||
"pretium. Curabitur non fringilla mi. Fusce ultricies, turpis " +
|
||||
"eu ultrices suscipit, ligula nisi consectetur eros, dapibus " +
|
||||
"aliquet dui sapien a turpis. Donec ultricies varius ligula, " +
|
||||
"ut hendrerit arcu malesuada at. Praesent sed elit pretium " +
|
||||
"eros luctus gravida. In ac dolor lorem. Cras condimentum " +
|
||||
"convallis elementum. Phasellus vel felis in nulla ultrices " +
|
||||
"venenatis. Nam non tortor non orci convallis convallis. " +
|
||||
"Nam tristique lacinia hendrerit. Pellentesque habitant morbi " +
|
||||
"tristique senectus et netus et malesuada fames ac turpis " +
|
||||
"egestas. Vivamus cursus, nibh eu imperdiet porta, magna " +
|
||||
"ipsum mollis mauris, sit amet fringilla mi nisl eu mi. " +
|
||||
"Phasellus posuere, leo at ultricies vehicula, massa risus " +
|
||||
"volutpat sapien, eu tincidunt diam ipsum eget nulla. Cras " +
|
||||
"molestie dapibus commodo. Ut vel tellus at massa gravida " +
|
||||
"semper non sed orci.";
|
||||
|
||||
public void testInputStream() throws Exception {
|
||||
int[] flagses = { Base64.DEFAULT,
|
||||
Base64.NO_PADDING,
|
||||
Base64.NO_WRAP,
|
||||
Base64.NO_PADDING | Base64.NO_WRAP,
|
||||
Base64.CRLF,
|
||||
Base64.WEB_SAFE };
|
||||
int[] writeLengths = { -10, -5, -1, 0, 1, 1, 2, 2, 3, 10, 100 };
|
||||
Random rng = new Random(32176L);
|
||||
|
||||
// Test input needs to be at least 2048 bytes to fill up the
|
||||
// read buffer of Base64InputStream.
|
||||
byte[] plain = (lipsum + lipsum + lipsum + lipsum + lipsum).getBytes();
|
||||
|
||||
for (int flags: flagses) {
|
||||
byte[] encoded = Base64.encode(plain, flags);
|
||||
|
||||
ByteArrayInputStream bais;
|
||||
Base64InputStream b64is;
|
||||
byte[] actual = new byte[plain.length * 2];
|
||||
int ap;
|
||||
int b;
|
||||
|
||||
// ----- test decoding ("encoded" -> "plain") -----
|
||||
|
||||
// read as much as it will give us in one chunk
|
||||
bais = new ByteArrayInputStream(encoded);
|
||||
b64is = new Base64InputStream(bais, flags);
|
||||
ap = 0;
|
||||
while ((b = b64is.read(actual, ap, actual.length-ap)) != -1) {
|
||||
ap += b;
|
||||
}
|
||||
assertEquals(actual, ap, plain);
|
||||
|
||||
// read individual bytes
|
||||
bais = new ByteArrayInputStream(encoded);
|
||||
b64is = new Base64InputStream(bais, flags);
|
||||
ap = 0;
|
||||
while ((b = b64is.read()) != -1) {
|
||||
actual[ap++] = (byte) b;
|
||||
}
|
||||
assertEquals(actual, ap, plain);
|
||||
|
||||
// mix reads of variously-sized arrays with one-byte reads
|
||||
bais = new ByteArrayInputStream(encoded);
|
||||
b64is = new Base64InputStream(bais, flags);
|
||||
ap = 0;
|
||||
readloop: while (true) {
|
||||
int l = writeLengths[rng.nextInt(writeLengths.length)];
|
||||
if (l >= 0) {
|
||||
b = b64is.read(actual, ap, l);
|
||||
if (b == -1) break readloop;
|
||||
ap += b;
|
||||
} else {
|
||||
for (int i = 0; i < -l; ++i) {
|
||||
if ((b = b64is.read()) == -1) break readloop;
|
||||
actual[ap++] = (byte) b;
|
||||
}
|
||||
}
|
||||
}
|
||||
assertEquals(actual, ap, plain);
|
||||
|
||||
// ----- test encoding ("plain" -> "encoded") -----
|
||||
|
||||
// read as much as it will give us in one chunk
|
||||
bais = new ByteArrayInputStream(plain);
|
||||
b64is = new Base64InputStream(bais, flags, true);
|
||||
ap = 0;
|
||||
while ((b = b64is.read(actual, ap, actual.length-ap)) != -1) {
|
||||
ap += b;
|
||||
}
|
||||
assertEquals(actual, ap, encoded);
|
||||
|
||||
// read individual bytes
|
||||
bais = new ByteArrayInputStream(plain);
|
||||
b64is = new Base64InputStream(bais, flags, true);
|
||||
ap = 0;
|
||||
while ((b = b64is.read()) != -1) {
|
||||
actual[ap++] = (byte) b;
|
||||
}
|
||||
assertEquals(actual, ap, encoded);
|
||||
|
||||
// mix reads of variously-sized arrays with one-byte reads
|
||||
bais = new ByteArrayInputStream(plain);
|
||||
b64is = new Base64InputStream(bais, flags, true);
|
||||
ap = 0;
|
||||
readloop: while (true) {
|
||||
int l = writeLengths[rng.nextInt(writeLengths.length)];
|
||||
if (l >= 0) {
|
||||
b = b64is.read(actual, ap, l);
|
||||
if (b == -1) break readloop;
|
||||
ap += b;
|
||||
} else {
|
||||
for (int i = 0; i < -l; ++i) {
|
||||
if ((b = b64is.read()) == -1) break readloop;
|
||||
actual[ap++] = (byte) b;
|
||||
}
|
||||
}
|
||||
}
|
||||
assertEquals(actual, ap, encoded);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that Base64OutputStream produces exactly the same results
|
||||
* as calling Base64.encode/.decode on an in-memory array.
|
||||
@ -286,125 +418,103 @@ public class Base64Test extends TestCase {
|
||||
int[] writeLengths = { -10, -5, -1, 0, 1, 1, 2, 2, 3, 10, 100 };
|
||||
Random rng = new Random(32176L);
|
||||
|
||||
// input needs to be at least 1024 bytes to test filling up
|
||||
// the write(int) buffer.
|
||||
byte[] input = ("Lorem ipsum dolor sit amet, consectetur adipiscing elit. " +
|
||||
"Quisque congue eleifend odio, eu ornare nulla facilisis eget. " +
|
||||
"Integer eget elit diam, sit amet laoreet nibh. Quisque enim " +
|
||||
"urna, pharetra vitae consequat eget, adipiscing eu ante. " +
|
||||
"Aliquam venenatis arcu nec nibh imperdiet tempor. In id dui " +
|
||||
"eget lorem aliquam rutrum vel vitae eros. In placerat ornare " +
|
||||
"pretium. Curabitur non fringilla mi. Fusce ultricies, turpis " +
|
||||
"eu ultrices suscipit, ligula nisi consectetur eros, dapibus " +
|
||||
"aliquet dui sapien a turpis. Donec ultricies varius ligula, " +
|
||||
"ut hendrerit arcu malesuada at. Praesent sed elit pretium " +
|
||||
"eros luctus gravida. In ac dolor lorem. Cras condimentum " +
|
||||
"convallis elementum. Phasellus vel felis in nulla ultrices " +
|
||||
"venenatis. Nam non tortor non orci convallis convallis. " +
|
||||
"Nam tristique lacinia hendrerit. Pellentesque habitant morbi " +
|
||||
"tristique senectus et netus et malesuada fames ac turpis " +
|
||||
"egestas. Vivamus cursus, nibh eu imperdiet porta, magna " +
|
||||
"ipsum mollis mauris, sit amet fringilla mi nisl eu mi. " +
|
||||
"Phasellus posuere, leo at ultricies vehicula, massa risus " +
|
||||
"volutpat sapien, eu tincidunt diam ipsum eget nulla. Cras " +
|
||||
"molestie dapibus commodo. Ut vel tellus at massa gravida " +
|
||||
"semper non sed orci.").getBytes();
|
||||
// Test input needs to be at least 1024 bytes to test filling
|
||||
// up the write(int) buffer of Base64OutputStream.
|
||||
byte[] plain = (lipsum + lipsum).getBytes();
|
||||
|
||||
for (int f = 0; f < flagses.length; ++f) {
|
||||
int flags = flagses[f];
|
||||
|
||||
byte[] expected = Base64.encode(input, flags);
|
||||
for (int flags: flagses) {
|
||||
byte[] encoded = Base64.encode(plain, flags);
|
||||
|
||||
ByteArrayOutputStream baos;
|
||||
Base64OutputStream b64os;
|
||||
byte[] actual;
|
||||
int p;
|
||||
|
||||
// ----- test encoding ("input" -> "expected") -----
|
||||
// ----- test encoding ("plain" -> "encoded") -----
|
||||
|
||||
// one large write(byte[]) of the whole input
|
||||
baos = new ByteArrayOutputStream();
|
||||
b64os = new Base64OutputStream(baos, flags);
|
||||
b64os.write(input);
|
||||
b64os.write(plain);
|
||||
b64os.close();
|
||||
actual = baos.toByteArray();
|
||||
assertEquals(expected, actual);
|
||||
assertEquals(encoded, actual);
|
||||
|
||||
// many calls to write(int)
|
||||
baos = new ByteArrayOutputStream();
|
||||
b64os = new Base64OutputStream(baos, flags);
|
||||
for (int i = 0; i < input.length; ++i) {
|
||||
b64os.write(input[i]);
|
||||
for (int i = 0; i < plain.length; ++i) {
|
||||
b64os.write(plain[i]);
|
||||
}
|
||||
b64os.close();
|
||||
actual = baos.toByteArray();
|
||||
assertEquals(expected, actual);
|
||||
assertEquals(encoded, actual);
|
||||
|
||||
// intermixed sequences of write(int) with
|
||||
// write(byte[],int,int) of various lengths.
|
||||
baos = new ByteArrayOutputStream();
|
||||
b64os = new Base64OutputStream(baos, flags);
|
||||
p = 0;
|
||||
while (p < input.length) {
|
||||
while (p < plain.length) {
|
||||
int l = writeLengths[rng.nextInt(writeLengths.length)];
|
||||
l = Math.min(l, input.length-p);
|
||||
l = Math.min(l, plain.length-p);
|
||||
if (l >= 0) {
|
||||
b64os.write(input, p, l);
|
||||
b64os.write(plain, p, l);
|
||||
p += l;
|
||||
} else {
|
||||
l = Math.min(-l, input.length-p);
|
||||
l = Math.min(-l, plain.length-p);
|
||||
for (int i = 0; i < l; ++i) {
|
||||
b64os.write(input[p+i]);
|
||||
b64os.write(plain[p+i]);
|
||||
}
|
||||
p += l;
|
||||
}
|
||||
}
|
||||
b64os.close();
|
||||
actual = baos.toByteArray();
|
||||
assertEquals(expected, actual);
|
||||
assertEquals(encoded, actual);
|
||||
|
||||
// ----- test decoding ("expected" -> "input") -----
|
||||
// ----- test decoding ("encoded" -> "plain") -----
|
||||
|
||||
// one large write(byte[]) of the whole input
|
||||
baos = new ByteArrayOutputStream();
|
||||
b64os = new Base64OutputStream(baos, flags, false);
|
||||
b64os.write(expected);
|
||||
b64os.write(encoded);
|
||||
b64os.close();
|
||||
actual = baos.toByteArray();
|
||||
assertEquals(input, actual);
|
||||
assertEquals(plain, actual);
|
||||
|
||||
// many calls to write(int)
|
||||
baos = new ByteArrayOutputStream();
|
||||
b64os = new Base64OutputStream(baos, flags, false);
|
||||
for (int i = 0; i < expected.length; ++i) {
|
||||
b64os.write(expected[i]);
|
||||
for (int i = 0; i < encoded.length; ++i) {
|
||||
b64os.write(encoded[i]);
|
||||
}
|
||||
b64os.close();
|
||||
actual = baos.toByteArray();
|
||||
assertEquals(input, actual);
|
||||
assertEquals(plain, actual);
|
||||
|
||||
// intermixed sequences of write(int) with
|
||||
// write(byte[],int,int) of various lengths.
|
||||
baos = new ByteArrayOutputStream();
|
||||
b64os = new Base64OutputStream(baos, flags, false);
|
||||
p = 0;
|
||||
while (p < expected.length) {
|
||||
while (p < encoded.length) {
|
||||
int l = writeLengths[rng.nextInt(writeLengths.length)];
|
||||
l = Math.min(l, expected.length-p);
|
||||
l = Math.min(l, encoded.length-p);
|
||||
if (l >= 0) {
|
||||
b64os.write(expected, p, l);
|
||||
b64os.write(encoded, p, l);
|
||||
p += l;
|
||||
} else {
|
||||
l = Math.min(-l, expected.length-p);
|
||||
l = Math.min(-l, encoded.length-p);
|
||||
for (int i = 0; i < l; ++i) {
|
||||
b64os.write(expected[p+i]);
|
||||
b64os.write(encoded[p+i]);
|
||||
}
|
||||
p += l;
|
||||
}
|
||||
}
|
||||
b64os.close();
|
||||
actual = baos.toByteArray();
|
||||
assertEquals(input, actual);
|
||||
assertEquals(plain, actual);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user