package net.varkhan.base.conversion.character;
import net.varkhan.base.conversion.AbstractEncoder;
import net.varkhan.base.conversion.Encoder;
import net.varkhan.base.conversion.serializer.EncodingException;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.BufferOverflowException;
import java.nio.ByteBuffer;
import java.nio.ReadOnlyBufferException;
/**
* <b></b>.
* <p/>
*
* @author varkhan
* @date 11/23/13
* @time 2:04 PM
*/
public class Base64Encoder<C> extends AbstractEncoder<CharSequence,C> implements Encoder<CharSequence,C> {
public static final Base64Encoder<?> UTF7=new Base64Encoder<Object>(Base64Decoder.CHARMAP_UTF7, '=');
public static final Base64Encoder<?> URL =new Base64Encoder<Object>(Base64Decoder.CHARMAP_URL, Base64Decoder.NO_PADDING);
public static final Base64Encoder<?> XML =new Base64Encoder<Object>(Base64Decoder.CHARMAP_XML, Base64Decoder.NO_PADDING);
protected final char[] map;
protected final byte[] pam;
protected final char pad;
public Base64Encoder() {
this(Base64Decoder.CHARMAP_UTF7, '=');
}
public Base64Encoder(char[] map) {
this(map, '=');
}
public Base64Encoder(char[] map, char pad) {
this.map=map;
this.pad=pad;
this.pam=_mapping(map);
}
public char[] getCharacterMap() { return map.clone(); }
public long encode(CharSequence obj, OutputStream stm, C ctx) { return _encode(pam, pad, obj, stm); }
public long encode(CharSequence obj, ByteBuffer buf, C ctx) { return _encode(pam, pad, obj, buf); }
public long encode(CharSequence obj, byte[] dat, long pos, long len, C ctx) {
return _encode(pam, pad, obj, dat, pos, len);
}
public long length(CharSequence obj, C ctx) { return _length(obj, pad); }
public static long _encode(final byte[] pam, char pad, CharSequence obj, OutputStream stm) {
try {
long len=0;
byte b0=0, b1=0, b2=0;
int i=0;
while(i<obj.length()) {
char c=obj.charAt(i);
// If we have padding characters, exit loop
if(pad!=Base64Decoder.NO_PADDING&&c==pad) break;
if(c>=pam.length) throw new EncodingException("Invalid character '"+c+"'");
int v = pam[c]-1;
if(v<0) throw new EncodingException("Invalid character '"+c+"'");
switch(i%4) {
case 0:
// ******.. ........ ........
b0 |= (0x3F&v)<<2;
break;
case 1:
// ======** ****.... ........
b0 |= (0x30&v)>>>4;
b1 |= (0x0F&v)<<4;
stm.write(b0);
len++;
break;
case 2:
// ________ ====**** **......
b1 |= (0x3C&v)>>>2;
b2 |= (0x03&v)<<6;
stm.write(b1);
len++;
break;
case 3:
// ________ ________ ==******
b2 |= 0x3F&v;
stm.write(b2);
b0=b1=b2=0;
len++;
break;
}
i++;
}
return len;
}
catch(IOException e) {
throw new EncodingException(e);
}
}
public static long _encode(final byte[] pam, char pad, CharSequence obj, ByteBuffer buf) {
try {
long len=0;
byte b0=0, b1=0, b2=0;
int i=0;
while(i<obj.length()) {
char c=obj.charAt(i);
// If we have padding characters, exit loop
if(pad!=Base64Decoder.NO_PADDING&&c==pad) break;
if(c>=pam.length) throw new EncodingException("Invalid character '"+c+"'");
int v = pam[c]-1;
if(v<0) throw new EncodingException("Invalid character '"+c+"'");
switch(i%4) {
case 0:
// ******.. ........ ........
b0 |= (0x3F&v)<<2;
break;
case 1:
// ======** ****.... ........
b0 |= (0x30&v)>>>4;
b1 |= (0x0F&v)<<4;
buf.put(b0);
len++;
break;
case 2:
// ________ ====**** **......
b1 |= (0x3C&v)>>>2;
b2 |= (0x03&v)<<6;
buf.put(b1);
len++;
break;
case 3:
// ________ ________ ==******
b2 |= 0x3F&v;
buf.put(b2);
b0=b1=b2=0;
len++;
break;
}
i++;
}
return len;
}
catch(BufferOverflowException e) {
throw new EncodingException(e);
}
catch(ReadOnlyBufferException e) {
throw new EncodingException(e);
}
}
public static long _encode(final byte[] pam, char pad, CharSequence obj, byte[] dat, long pos, long len) {
try {
int p=(int) pos;
byte b0=0, b1=0, b2=0;
int i=0;
while(i<obj.length() && p<pos+len) {
char c=obj.charAt(i);
// If we have padding characters, exit loop
if(pad!=Base64Decoder.NO_PADDING&&c==pad) break;
if(c>=pam.length) throw new EncodingException("Invalid character '"+c+"'");
int v = pam[c]-1;
if(v<0) throw new EncodingException("Invalid character '"+c+"'");
switch(i%4) {
case 0:
// ******.. ........ ........
b0 |= (0x3F&v)<<2;
break;
case 1:
// ======** ****.... ........
b0 |= (0x30&v)>>>4;
b1 |= (0x0F&v)<<4;
dat[p++] = b0;
break;
case 2:
// ________ ====**** **......
b1 |= (0x3C&v)>>>2;
b2 |= (0x03&v)<<6;
dat[p++] = b1;
break;
case 3:
// ________ ________ ==******
b2 |= 0x3F&v;
dat[p++] = b2;
b0=b1=b2=0;
break;
}
i++;
}
return p-pos;
}
catch(ArrayIndexOutOfBoundsException e) {
throw new EncodingException(e);
}
}
public static long _length(CharSequence obj, char pad) {
final int len = obj.length();
int p = 0;
if(pad!=Base64Decoder.NO_PADDING) {
if(len>1 && obj.charAt(len-1)==pad) p++;
if(len>2 && obj.charAt(len-2)==pad) p++;
}
return (len*3-p)/4;
}
public static byte[] _mapping(char[] map) {
if(map.length!=64) throw new IllegalArgumentException("Character map should have exactly 64 characters");
int max=0;
for(char c: map) if(max<c) max=c;
byte[] pam = new byte[max+1];
for(int i=0;i<64;) {
pam[map[i]]=(byte)++i;
}
return pam;
}
}