/*
* Base64Encoder.java
*
* Created on 20 December 2005, 11:15
*
*/
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.nio.charset.CharsetEncoder;
import java.nio.charset.CoderResult;
/**
* Base-64 Encoder - translates from base-64 text into binary.
* Whitespace is ignored, so is anything following a terminating =
* character.
* @author Malcolm McMahon
* @version $Revision: 1.1 $
*/
public class Base64Encoder extends CharsetEncoder {
final static int CHARCODE_INVALID = -1;
final static int CHARCODE_PADDER = -2;
final static int CHARCODE_WHITESPACE = -3;
int[] encTable;
int encState;
Byte excessByte;
int bits;
/** Creates a new instance of Base64Encoder
* @param cs The Charset which created this
* @param encTable Table which maps chacacters onto 6 bit values.
*/
public Base64Encoder(Charset cs, int[] encTable) {
super(cs, 1.4f, 2f);
this.encTable = encTable;
}
private boolean out(ByteBuffer bb, int outValue) {
if(bb.remaining() > 0) {
bb.put((byte)outValue);
return true;
} else {
excessByte = Byte.valueOf((byte)outValue);
return false;
}
}
/**
* Flushes this encoder.
*
* The default implementation of this method does nothing, and always
* returns {@link CoderResult#UNDERFLOW}. This method should be overridden
* by encoders that may need to write final bytes to the output buffer
* once the entire input sequence has been read.
*
* @param out
* The output byte buffer
*
* @return A coder-result object, either {@link CoderResult#UNDERFLOW} or
* {@link CoderResult#OVERFLOW}
*/
protected java.nio.charset.CoderResult implFlush(java.nio.ByteBuffer out) {
if(encState != 0 && encState != 4)
throw new IllegalArgumentException("Base-64 text ends prematurely");
if(excessByte == null) {
implReset();
return CoderResult.UNDERFLOW;
}
if(out.remaining() > 0) {
out.put(excessByte.byteValue());
implReset();
return CoderResult.UNDERFLOW;
} else
return CoderResult.OVERFLOW;
}
/**
* Encodes one or more characters into one or more bytes.
*
* This method encapsulates the basic encoding loop, encoding as many
* characters as possible until it either runs out of input, runs out of room
* in the output buffer, or encounters an encoding error. This method is
* invoked by the {@link #encode encode} method, which handles result
* interpretation and error recovery.
*
*
The buffers are read from, and written to, starting at their current
* positions. At most {@link Buffer#remaining in.remaining()} characters
* will be read, and at most {@link Buffer#remaining out.remaining()}
* bytes will be written. The buffers' positions will be advanced to
* reflect the characters read and the bytes written, but their marks and
* limits will not be modified.
*
*
This method returns a {@link CoderResult} object to describe its
* reason for termination, in the same manner as the {@link #encode encode}
* method. Most implementations of this method will handle encoding errors
* by returning an appropriate result object for interpretation by the
* {@link #encode encode} method. An optimized implementation may instead
* examine the relevant error action and implement that action itself.
*
*
An implementation of this method may perform arbitrary lookahead by
* returning {@link CoderResult#UNDERFLOW} until it receives sufficient
* input.
*
* @param in
* The input character buffer
*
* @param out
* The output byte buffer
*
* @return A coder-result object describing the reason for termination
*/
public java.nio.charset.CoderResult encodeLoop(java.nio.CharBuffer in, java.nio.ByteBuffer out) {
if(excessByte != null) {
if(out.remaining() > 0) {
out.put(excessByte.byteValue());
excessByte = null;
} else
return CoderResult.OVERFLOW;
}
while(in.remaining() > 0) {
char inch = in.get();
int code = (int)inch >= encTable.length ? CHARCODE_INVALID : encTable[(int)inch];
if(encState < 4) {
switch(code) {
case CHARCODE_INVALID:
throw new IllegalArgumentException("Invalid base-64 character'" + inch + "'" );
case CHARCODE_WHITESPACE:
break;
case CHARCODE_PADDER:
if(encState == 1)
throw new IllegalArgumentException("Mal-formed base-64 (= after one character");
encState = 4;
break;
default:
switch(encState) {
case 0:
bits = code << 2;
encState = 1;
break;
case 1:
encState = 2;
int v = bits | ((code >> 4) & 3);
bits = (code << 4) & 0xF0;
if(!out(out, v))
return CoderResult.OVERFLOW;
break;
case 2:
encState = 3;
v = bits | (code >> 2) & 0x0f;
bits = (code << 6) & 0xC0;
if(!out(out, v))
return CoderResult.OVERFLOW;
break;
case 3:
encState = 0;
bits |= (code & 0x3f);
if(!out(out, bits))
return CoderResult.OVERFLOW;
break;
}
break;
}
}
}
return CoderResult.UNDERFLOW;
}
/**
* Reset - clear encoder state
*/
protected void implReset() {
encState = 0;
bits = 0;
excessByte = null;
}
}