/* See LICENSE for licensing and NOTICE for copyright. */
package org.cryptacular.codec;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import org.cryptacular.EncodingException;
/**
* Base encoder class for encoding schemes described in RFC 3548.
*
* @author Middleware Services
*/
public abstract class AbstractBaseNEncoder implements Encoder
{
/** Platform-specific line terminator string, e.g. LF (Unix), CRLF (Windows). */
private static final String NEWLINE = System.lineSeparator();
/** Number of base64 characters per line. */
protected final int lineLength;
/** Encoding character set. */
private final char[] charset;
/** Number of bits in a block. */
private final int blockLength = getBlockLength();
/** Number of bits encoding a single character. */
private final int bitsPerChar = getBitsPerChar();
/** Initial bit mask for selecting characters in a block. */
private final long initialBitMask;
/** Holds a block of bytes to encode. */
private long block;
/** Number of bits in encode block remaining. */
private int remaining = blockLength;
/** Number of characters written. */
private int outCount;
/**
* Creates a new instance with given parameters.
*
* @param characterSet Encoding character set.
* @param charactersPerLine Number of characters per line.
*/
public AbstractBaseNEncoder(final char[] characterSet, final int charactersPerLine)
{
charset = characterSet;
long mask = 0;
for (int i = 1; i <= bitsPerChar; i++) {
mask |= 1L << (blockLength - i);
}
initialBitMask = mask;
lineLength = charactersPerLine;
}
@Override
public void encode(final ByteBuffer input, final CharBuffer output) throws EncodingException
{
while (input.hasRemaining()) {
remaining -= 8;
block |= (input.get() & 0xffL) << remaining;
if (remaining == 0) {
writeOutput(output, 0);
}
}
}
@Override
public void finalize(final CharBuffer output) throws EncodingException
{
if (remaining < blockLength) {
// Floor division
final int stop = remaining / bitsPerChar * bitsPerChar;
writeOutput(output, stop);
for (int i = stop; i > 0; i -= bitsPerChar) {
output.put('=');
}
}
// Append trailing newline to make consistent with OpenSSL base64 output
if (lineLength > 0 && output.position() > 0) {
output.append(NEWLINE);
}
outCount = 0;
}
@Override
public int outputSize(final int inputSize)
{
int len = (inputSize + (blockLength / 8) - 1) * 8 / bitsPerChar;
if (lineLength > 0) {
len += (len / lineLength + 1) * NEWLINE.length();
}
return len;
}
/** @return Number of bits in a block of encoded characters. */
protected abstract int getBlockLength();
/** @return Number of bits encoding a single character. */
protected abstract int getBitsPerChar();
/**
* Writes bytes in the current encoding block to the output buffer.
*
* @param output Output buffer.
* @param stop Bit shift stop position where data in current encoding block ends.
*/
private void writeOutput(final CharBuffer output, final int stop)
{
int shift = blockLength;
long mask = initialBitMask;
int index;
while (shift > stop) {
shift -= bitsPerChar;
index = (int) ((block & mask) >> shift);
output.put(charset[index]);
outCount++;
if (lineLength > 0 && outCount % lineLength == 0) {
output.put(NEWLINE);
}
mask >>= bitsPerChar;
}
block = 0;
remaining = blockLength;
}
}