package com.revolsys.util;
/**
* A {@link Base64InputStream} will read data from another
* <tt>java.io.InputStream</tt>, given in the constructor, and encode/decode
* to/from Base64 notation on the fly.
*
* @see Base64
* @since 1.3
*/
public class Base64InputStream extends java.io.FilterInputStream {
private final boolean breakLines; // Break lines at less than 80 characters
private final byte[] buffer; // Small buffer holding converted data
private final int bufferLength; // Length of buffer (3 or 4)
private final byte[] decodabet; // Local copies to avoid extra method calls
private final boolean encode; // Encoding or decoding
private int lineLength;
private int numSigBytes; // Number of meaningful bytes in the buffer
private final int options; // Record options used to create the stream.
private int position; // Current position in the buffer
/**
* Constructs a {@link Base64InputStream} in DECODE mode.
*
* @param in the <tt>java.io.InputStream</tt> from which to read data.
* @since 1.3
*/
public Base64InputStream(final java.io.InputStream in) {
this(in, Base64.DECODE);
}
/**
* Constructs a {@link Base64InputStream} in either ENCODE or DECODE mode.
* <p>
* Valid options:
*
* <pre>
* ENCODE or DECODE: Encode or Decode as data is read.
* DONT_BREAK_LINES: don't break lines at 76 characters
* (only meaningful when encoding)
* <i>Note: Technically, this makes your encoding non-compliant.</i>
* </pre>
* <p>
* Example: <code>new Base64.InputStream( in, Base64.DECODE )</code>
*
* @param in the <tt>java.io.InputStream</tt> from which to read data.
* @param options Specified options
* @see Base64#ENCODE
* @see Base64#DECODE
* @see Base64#DONT_BREAK_LINES
* @since 2.0
*/
public Base64InputStream(final java.io.InputStream in, final int options) {
super(in);
this.breakLines = (options & Base64.DONT_BREAK_LINES) != Base64.DONT_BREAK_LINES;
this.encode = (options & Base64.ENCODE) == Base64.ENCODE;
this.bufferLength = this.encode ? 4 : 3;
this.buffer = new byte[this.bufferLength];
this.position = -1;
this.lineLength = 0;
this.options = options; // Record for later, mostly to determine which
// alphabet to use
this.decodabet = Base64.getDecodabet(options);
}
/**
* Reads enough of the input stream to convert to/from Base64 and returns
* the next byte.
*
* @return next byte
* @since 1.3
*/
@Override
public int read() throws java.io.IOException {
// Do we need to get data?
if (this.position < 0) {
if (this.encode) {
final byte[] b3 = new byte[3];
int numBinaryBytes = 0;
for (int i = 0; i < 3; i++) {
try {
final int b = this.in.read();
// If end of stream, b is -1.
if (b >= 0) {
b3[i] = (byte)b;
numBinaryBytes++;
} // end if: not end of stream
} // end try: read
catch (final java.io.IOException e) {
// Only a problem if we got no data at all.
if (i == 0) {
throw e;
}
} // end catch
} // end for: each needed input byte
if (numBinaryBytes > 0) {
Base64.encode3to4(b3, 0, numBinaryBytes, this.buffer, 0, this.options);
this.position = 0;
this.numSigBytes = 4;
} // end if: got data
else {
return -1;
} // end else
} // end if: encoding
// Else decoding
else {
final byte[] b4 = new byte[4];
int i = 0;
for (i = 0; i < 4; i++) {
// Read four "meaningful" bytes:
int b = 0;
do {
b = this.in.read();
} while (b >= 0 && this.decodabet[b & 0x7f] <= Base64.WHITE_SPACE_ENC);
if (b < 0) {
break; // Reads a -1 if end of stream
}
b4[i] = (byte)b;
} // end for: each needed input byte
if (i == 4) {
this.numSigBytes = Base64.decode4to3(b4, 0, this.buffer, 0, this.options);
this.position = 0;
} // end if: got four characters
else if (i == 0) {
return -1;
} // end else if: also padded correctly
else {
// Must have broken out from above.
throw new java.io.IOException("Improperly padded Base64 input.");
} // end
} // end else: decode
} // end else: get data
// Got data?
if (this.position >= 0) {
// End of relevant data?
if ( /* !encode && */this.position >= this.numSigBytes) {
return -1;
}
if (this.encode && this.breakLines && this.lineLength >= Base64.MAX_LINE_LENGTH) {
this.lineLength = 0;
return '\n';
} // end if
else {
this.lineLength++; // This isn't important when decoding
// but throwing an extra "if" seems
// just as wasteful.
final int b = this.buffer[this.position++];
if (this.position >= this.bufferLength) {
this.position = -1;
}
return b & 0xFF; // This is how you "cast" a byte that's
// intended to be unsigned.
} // end else
} // end if: position >= 0
// Else error
else {
// When JDK1.4 is more accepted, use an assertion here.
throw new java.io.IOException("Error in Base64 code reading stream.");
} // end else
}
/**
* Calls {@link #read()} repeatedly until the end of stream is reached or
* <var>len</var> bytes are read. Returns number of bytes read into array or
* -1 if end of stream is encountered.
*
* @param dest array to hold values
* @param off offset for array
* @param len max number of bytes to read into array
* @return bytes read into array or -1 if end of stream is encountered.
* @since 1.3
*/
@Override
public int read(final byte[] dest, final int off, final int len) throws java.io.IOException {
int i;
int b;
for (i = 0; i < len; i++) {
b = read();
// if( b < 0 && i == 0 )
// return -1;
if (b >= 0) {
dest[off + i] = (byte)b;
} else if (i == 0) {
return -1;
} else {
break; // Out of 'for' loop
}
} // end for: each byte read
return i;
}
}