Created
July 30, 2012 05:16
-
-
Save rstiller/3204952 to your computer and use it in GitHub Desktop.
B64
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package test; | |
import java.io.IOException; | |
import java.io.OutputStream; | |
public class Base64OutputStream extends OutputStream { | |
public static final int DEFAULT_BUFFER_SIZE = 65536; | |
public static final String DEFAULT_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; | |
public static final String BASE64_URL_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; | |
public static final char DEFAULT_PADDING = '='; | |
private class CombinedByteArrayWrapper { | |
protected byte[] data; | |
protected int length; | |
protected int offset; | |
protected int difference; | |
public int getLength() { | |
return length + difference; | |
} | |
public int getOffset() { | |
return offset; | |
} | |
public void setLength(final int newLength) { | |
length = newLength; | |
} | |
public void setOffset(final int newOffset) { | |
offset = newOffset; | |
} | |
public void setData(final byte[] newData) { | |
difference = overflow[0] != -1 ? 1 : 0; | |
difference = overflow[1] != -1 ? 2 : difference; | |
data = newData; | |
} | |
public byte get(final int index) { | |
return difference > 0 && index < difference ? overflow[index - offset] : data[index - difference]; | |
} | |
} | |
protected char[] alphabet = DEFAULT_ALPHABET.toCharArray(); | |
protected char padding = DEFAULT_PADDING; | |
protected byte[] buffer; | |
protected byte[] overflow = new byte[2]; | |
protected int currentBufferIndex; | |
protected CombinedByteArrayWrapper wrapper = new CombinedByteArrayWrapper(); | |
OutputStream stream; | |
byte[] singleByte = new byte[1]; | |
public Base64OutputStream() { | |
buffer = new byte[DEFAULT_BUFFER_SIZE]; | |
} | |
public Base64OutputStream(final String alphabet, final char padding, final int bufferSize) { | |
if (bufferSize % 4 != 0) { | |
throw new IllegalArgumentException("bufferSize needs to be restless dividable through 4"); | |
} | |
this.alphabet = alphabet.toCharArray(); | |
this.padding = padding; | |
buffer = new byte[bufferSize]; | |
} | |
public void reset(final OutputStream stream) { | |
this.stream = stream; | |
currentBufferIndex = 0; | |
overflow[0] = overflow[1] = -1; | |
} | |
@Override | |
public void write(final int b) throws IOException { | |
singleByte[0] = (byte) b; | |
write(singleByte, 0, singleByte.length); | |
} | |
@Override | |
public void write(final byte[] data, final int offset, final int length) throws IOException { | |
wrapper.setData(data); | |
wrapper.setLength(length); | |
wrapper.setOffset(offset); | |
decode(); | |
} | |
protected void decode() throws IOException { | |
int offset = wrapper.getOffset(), pos = offset, index, end, rest, length = wrapper.getLength(); | |
end = length - 3; | |
for (int i = 0; i <= end; i += 3, pos += 3, currentBufferIndex += 4) { | |
index = (wrapper.get(pos) & 0xfc) >> 2; | |
buffer[currentBufferIndex] = (byte) alphabet[index]; | |
index = ((wrapper.get(pos) & 0x03) << 4) | ((wrapper.get(pos + 1) & 0xf0) >> 4); | |
buffer[currentBufferIndex + 1] = (byte) alphabet[index]; | |
index = ((wrapper.get(pos + 1) & 0x0f) << 2) | ((wrapper.get(pos + 2) & 0xc0) >> 6); | |
buffer[currentBufferIndex + 2] = (byte) alphabet[index]; | |
index = wrapper.get(pos + 2) & 0x3f; | |
buffer[currentBufferIndex + 3] = (byte) alphabet[index]; | |
if (currentBufferIndex + 4 == buffer.length) { | |
stream.write(buffer, 0, buffer.length); | |
currentBufferIndex = -4; | |
} | |
} | |
overflow[0] = overflow[1] = -1; | |
if (length % 3 != 0) { | |
rest = length - (pos - offset); | |
if (rest == 2) { | |
overflow[0] = wrapper.get(pos); | |
overflow[1] = wrapper.get(pos + 1); | |
} else if (rest == 1) { | |
overflow[0] = wrapper.get(pos); | |
overflow[1] = -1; | |
} | |
} | |
} | |
@Override | |
public void flush() throws IOException { | |
int index; | |
if (overflow[1] != -1) { | |
index = (overflow[0] & 0xfc) >> 2; | |
buffer[currentBufferIndex + 0] = (byte) alphabet[index]; | |
index = ((overflow[0] & 0x03) << 4) | ((overflow[1] & 0xf0) >> 4); | |
buffer[currentBufferIndex + 1] = (byte) alphabet[index]; | |
index = (overflow[1] & 0x0f) << 2; | |
buffer[currentBufferIndex + 2] = (byte) alphabet[index]; | |
buffer[currentBufferIndex + 3] = (byte) padding; | |
currentBufferIndex += 4; | |
} else if (overflow[0] != -1) { | |
index = (overflow[0] & 0xfc) >> 2; | |
buffer[currentBufferIndex + 0] = (byte) alphabet[index]; | |
index = (overflow[0] & 0x03) << 4; | |
buffer[currentBufferIndex + 1] = (byte) alphabet[index]; | |
buffer[currentBufferIndex + 2] = (byte) padding; | |
buffer[currentBufferIndex + 3] = (byte) padding; | |
currentBufferIndex += 4; | |
} | |
if (currentBufferIndex != -4 && currentBufferIndex % 4 == 0) { | |
stream.write(buffer, 0, currentBufferIndex); | |
currentBufferIndex = 0; | |
} | |
overflow[0] = overflow[1] = -1; | |
super.flush(); | |
} | |
} | |
package test; | |
import static org.junit.Assert.assertEquals; | |
import java.io.ByteArrayOutputStream; | |
import java.nio.charset.Charset; | |
import org.junit.Ignore; | |
import org.junit.Test; | |
public class Base64OutputStreamTest { | |
Base64OutputStream output = new Base64OutputStream(Base64OutputStream.DEFAULT_ALPHABET, | |
Base64OutputStream.DEFAULT_PADDING, 4); | |
@Test | |
@Ignore | |
public void write() throws Exception { | |
ByteArrayOutputStream underlying = new ByteArrayOutputStream(); | |
output.reset(underlying); | |
underlying.reset(); | |
output.write("".getBytes(Charset.forName("utf-8"))); | |
output.flush(); | |
assertEquals("", new String(underlying.toByteArray())); | |
underlying.reset(); | |
output.write("f".getBytes(Charset.forName("utf-8"))); | |
output.flush(); | |
assertEquals("Zg==", new String(underlying.toByteArray())); | |
underlying.reset(); | |
output.write("fo".getBytes(Charset.forName("utf-8"))); | |
output.flush(); | |
assertEquals("Zm8=", new String(underlying.toByteArray())); | |
underlying.reset(); | |
output.write("foo".getBytes(Charset.forName("utf-8"))); | |
output.flush(); | |
assertEquals("Zm9v", new String(underlying.toByteArray())); | |
underlying.reset(); | |
output.write("foob".getBytes(Charset.forName("utf-8"))); | |
output.flush(); | |
assertEquals("Zm9vYg==", new String(underlying.toByteArray())); | |
underlying.reset(); | |
output.write("fooba".getBytes(Charset.forName("utf-8"))); | |
output.flush(); | |
assertEquals("Zm9vYmE=", new String(underlying.toByteArray())); | |
underlying.reset(); | |
output.write("foobar".getBytes(Charset.forName("utf-8"))); | |
output.flush(); | |
assertEquals("Zm9vYmFy", new String(underlying.toByteArray())); | |
} | |
@Test | |
public void realWorldError() throws Exception { | |
ByteArrayOutputStream underlying = new ByteArrayOutputStream(); | |
long value = System.currentTimeMillis() + 1000; | |
Long.toString(value); | |
byte[] longBuffer = longToBytes(value); | |
output = new Base64OutputStream(); | |
output.reset(underlying); | |
output.write("testuser".getBytes(Charset.forName("utf-8"))); | |
output.write((byte) ';'); | |
output.write(longBuffer); | |
output.write((byte) ';'); | |
output.flush(); | |
assertEquals("", new String(underlying.toByteArray())); | |
} | |
private long bytesToLong(final byte[] bytes) { | |
long tmp = 0; | |
tmp |= (long) (bytes[0] & 0xff) << 56; | |
tmp |= (long) (bytes[1] & 0xff) << 48; | |
tmp |= (long) (bytes[2] & 0xff) << 40; | |
tmp |= (long) (bytes[3] & 0xff) << 32; | |
tmp |= (long) (bytes[4] & 0xff) << 24; | |
tmp |= (long) (bytes[5] & 0xff) << 16; | |
tmp |= (long) (bytes[6] & 0xff) << 8; | |
tmp |= bytes[7] & 0xff; | |
return tmp; | |
} | |
private void printLong(final long value) { | |
byte[] longBuffer = longToBytes(value); | |
for (int i = 0; i < longBuffer.length; i++) { | |
System.out.print(printByte(longBuffer[i]) + '.'); | |
} | |
System.out.println(); | |
} | |
private byte[] longToBytes(final long value) { | |
byte[] longBuffer = new byte[8]; | |
longBuffer[0] = (byte) ((value >> 56) & 0xff); | |
longBuffer[1] = (byte) ((value >> 48) & 0xff); | |
longBuffer[2] = (byte) ((value >> 40) & 0xff); | |
longBuffer[3] = (byte) ((value >> 32) & 0xff); | |
longBuffer[4] = (byte) ((value >> 24) & 0xff); | |
longBuffer[5] = (byte) ((value >> 16) & 0xff); | |
longBuffer[6] = (byte) ((value >> 8) & 0xff); | |
longBuffer[7] = (byte) (value & 0xff); | |
return longBuffer; | |
} | |
private String printByte(final byte b) { | |
StringBuilder builder = new StringBuilder(); | |
builder.append((b & (byte) 128) == 128 ? '1' : '0'); | |
builder.append((b & (byte) 64) == 64 ? '1' : '0'); | |
builder.append((b & (byte) 32) == 32 ? '1' : '0'); | |
builder.append((b & (byte) 16) == 16 ? '1' : '0'); | |
builder.append((b & (byte) 8) == 8 ? '1' : '0'); | |
builder.append((b & (byte) 4) == 4 ? '1' : '0'); | |
builder.append((b & (byte) 2) == 2 ? '1' : '0'); | |
builder.append((b & (byte) 1) == 1 ? '1' : '0'); | |
return builder.toString(); | |
} | |
@Test | |
@Ignore | |
public void serialWrite_SmallerBuffer() throws Exception { | |
serialWrite("foo", "bar", "Zm9vYmFy", 4); | |
} | |
@Test | |
@Ignore | |
public void serialWrite_BiggerBuffer() throws Exception { | |
serialWrite("foo", "bar", "Zm9vYmFy", 40); | |
} | |
@Test | |
@Ignore | |
public void serialWrite_SmallerBuffer_Async1() throws Exception { | |
serialWrite("fo", "obar", "Zm9vYmFy", 4); | |
} | |
@Test | |
@Ignore | |
public void serialWrite_BiggerBuffer_Async1() throws Exception { | |
serialWrite("fo", "obar", "Zm9vYmFy", 40); | |
} | |
@Test | |
@Ignore | |
public void serialWrite_SmallerBuffer_Async2() throws Exception { | |
serialWrite("foob", "ar", "Zm9vYmFy", 4); | |
} | |
@Test | |
@Ignore | |
public void serialWrite_BiggerBuffer_Async2() throws Exception { | |
serialWrite("foob", "ar", "Zm9vYmFy", 40); | |
} | |
public void serialWrite(final String src1, final String src2, final String dest, final int bufferSize) | |
throws Exception { | |
ByteArrayOutputStream underlying = new ByteArrayOutputStream(); | |
byte[] data1 = src1.getBytes(Charset.forName("utf-8")); | |
byte[] data2 = src2.getBytes(Charset.forName("utf-8")); | |
output = new Base64OutputStream(Base64OutputStream.DEFAULT_ALPHABET, Base64OutputStream.DEFAULT_PADDING, | |
bufferSize); | |
output.reset(underlying); | |
output.write(data1); | |
output.write(data2); | |
output.flush(); | |
assertEquals(dest, new String(underlying.toByteArray())); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment