Java tutorial
/* * Copyright 1999,2005 The Apache Software Foundation. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStream; import java.io.StringWriter; import java.io.Writer; import java.lang.reflect.UndeclaredThrowableException; import org.xml.sax.ContentHandler; import org.xml.sax.SAXException; /** Performs Base64 encoding and/or decoding. This is an on-the-fly decoder: Unlike, * for example, the commons-codec classes, it doesn't depend on byte arrays. In * other words, it has an extremely low memory profile. This is well suited even * for very large byte streams. */ public class Base64 { /** An exception of this type is thrown, if the decoded * character stream contains invalid input. */ public static class DecodingException extends IOException { private static final long serialVersionUID = 3257006574836135478L; DecodingException(String pMessage) { super(pMessage); } } /** An exception of this type is thrown by the {@link SAXEncoder}, * if writing to the target handler causes a SAX exception. * This class is required, because the {@link IOException} * allows no cause until Java 1.3. */ public static class SAXIOException extends IOException { private static final long serialVersionUID = 3258131345216451895L; final SAXException saxException; SAXIOException(SAXException e) { super(); saxException = e; } /** Returns the encapsulated {@link SAXException}. * @return An exception, which was thrown when invoking * {@link ContentHandler#characters(char[], int, int)}. */ public SAXException getSAXException() { return saxException; } } /** Default line separator: \n */ public static final String LINE_SEPARATOR = "\n"; /** Default size for line wrapping. */ public static final int LINE_SIZE = 76; /** * This array is a lookup table that translates 6-bit positive integer * index values into their "Base64 Alphabet" equivalents as specified * in Table 1 of RFC 2045. */ private static final char intToBase64[] = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/' }; /** * This array is a lookup table that translates unicode characters * drawn from the "Base64 Alphabet" (as specified in Table 1 of RFC 2045) * into their 6-bit positive integer equivalents. Characters that * are not in the Base64 alphabet but fall within the bounds of the * array are translated to -1. */ private static final byte base64ToInt[] = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51 }; /** An encoder is an object, which is able to encode byte array * in blocks of three bytes. Any such block is converted into an * array of four bytes. */ public static abstract class Encoder { private int num, numBytes; private final char[] charBuffer; private int charOffset; private final int wrapSize; private final int skipChars; private final String sep; private int lineChars = 0; /** Creates a new instance. * @param pBuffer The encoders buffer. The encoder will * write to the buffer as long as possible. If the * buffer is full or the end of data is signaled, then * the method {@link #writeBuffer(char[], int, int)} * will be invoked. * @param pWrapSize A nonzero value indicates, that a line * wrap should be performed after the given number of * characters. The value must be a multiple of 4. Zero * indicates, that no line wrap should be performed. * @param pSep The eol sequence being used to terminate * a line in case of line wraps. May be null, in which * case the default value {@link Base64#LINE_SEPARATOR} * is being used. */ protected Encoder(char[] pBuffer, int pWrapSize, String pSep) { charBuffer = pBuffer; sep = pSep == null ? null : Base64.LINE_SEPARATOR; skipChars = pWrapSize == 0 ? 4 : 4 + sep.length(); wrapSize = skipChars == 4 ? 0 : pWrapSize; if (wrapSize < 0 || wrapSize % 4 > 0) { throw new IllegalArgumentException( "Illegal argument for wrap size: " + pWrapSize + "(Expected nonnegative multiple of 4)"); } if (pBuffer.length < skipChars) { throw new IllegalArgumentException( "The buffer must contain at least " + skipChars + " characters, but has " + pBuffer.length); } } /** Called for writing the buffer contents to the target. * @param pChars The buffer being written. * @param pOffset Offset of first character being written. * @param pLen Number of characters being written. * @throws IOException Writing to the destination failed. */ protected abstract void writeBuffer(char[] pChars, int pOffset, int pLen) throws IOException; private void wrap() { for (int j = 0; j < sep.length(); j++) { charBuffer[charOffset++] = sep.charAt(j); } lineChars = 0; } /** Encodes the given byte array. * @param pBuffer Byte array being encoded. * @param pOffset Offset of first byte being encoded. * @param pLen Number of bytes being encoded. * @throws IOException Invoking the {@link #writeBuffer(char[],int,int)} method * for writing the encoded data failed. */ public void write(byte[] pBuffer, int pOffset, int pLen) throws IOException { for (int i = 0; i < pLen; i++) { int b = pBuffer[pOffset++]; if (b < 0) { b += 256; } num = (num << 8) + b; if (++numBytes == 3) { charBuffer[charOffset++] = intToBase64[num >> 18]; charBuffer[charOffset++] = intToBase64[(num >> 12) & 0x3f]; charBuffer[charOffset++] = intToBase64[(num >> 6) & 0x3f]; charBuffer[charOffset++] = intToBase64[num & 0x3f]; if (wrapSize > 0) { lineChars += 4; if (lineChars >= wrapSize) { wrap(); } } num = 0; numBytes = 0; if (charOffset + skipChars > charBuffer.length) { writeBuffer(charBuffer, 0, charOffset); charOffset = 0; } } } } /** Writes any currently buffered data to the destination. * @throws IOException Invoking the {@link #writeBuffer(char[],int,int)} * method for writing the encoded data failed. */ public void flush() throws IOException { if (numBytes > 0) { if (numBytes == 1) { charBuffer[charOffset++] = intToBase64[num >> 2]; charBuffer[charOffset++] = intToBase64[(num << 4) & 0x3f]; charBuffer[charOffset++] = '='; charBuffer[charOffset++] = '='; } else { charBuffer[charOffset++] = intToBase64[num >> 10]; charBuffer[charOffset++] = intToBase64[(num >> 4) & 0x3f]; charBuffer[charOffset++] = intToBase64[(num << 2) & 0x3f]; charBuffer[charOffset++] = '='; } lineChars += 4; num = 0; numBytes = 0; } if (wrapSize > 0 && lineChars > 0) { wrap(); } if (charOffset > 0) { writeBuffer(charBuffer, 0, charOffset); charOffset = 0; } } } /** An {@link OutputStream}, which is writing to the given * {@link Encoder}. */ public static class EncoderOutputStream extends OutputStream { private final Encoder encoder; /** Creates a new instance, which is creating * output using the given {@link Encoder}. * @param pEncoder The base64 encoder being used. */ public EncoderOutputStream(Encoder pEncoder) { encoder = pEncoder; } private final byte[] oneByte = new byte[1]; public void write(int b) throws IOException { oneByte[0] = (byte) b; encoder.write(oneByte, 0, 1); } public void write(byte[] pBuffer, int pOffset, int pLen) throws IOException { encoder.write(pBuffer, pOffset, pLen); } public void close() throws IOException { encoder.flush(); } } /** Returns an {@link OutputStream}, that encodes its input in Base64 * and writes it to the given {@link Writer}. If the Base64 stream * ends, then the output streams {@link OutputStream#close()} method * must be invoked. Note, that this will <em>not</em> close the * target {@link Writer}! * @param pWriter Target writer. * @return An output stream, encoding its input in Base64 and writing * the output to the writer <code>pWriter</code>. */ public static OutputStream newEncoder(Writer pWriter) { return newEncoder(pWriter, LINE_SIZE, LINE_SEPARATOR); } /** Returns an {@link OutputStream}, that encodes its input in Base64 * and writes it to the given {@link Writer}. If the Base64 stream * ends, then the output streams {@link OutputStream#close()} method * must be invoked. Note, that this will <em>not</em> close the * target {@link Writer}! * @param pWriter Target writer. * @param pLineSize Size of one line in characters, must be a multiple * of four. Zero indicates, that no line wrapping should occur. * @param pSeparator Line separator or null, in which case the default value * {@link #LINE_SEPARATOR} is used. * @return An output stream, encoding its input in Base64 and writing * the output to the writer <code>pWriter</code>. */ public static OutputStream newEncoder(final Writer pWriter, int pLineSize, String pSeparator) { final Encoder encoder = new Encoder(new char[4096], pLineSize, pSeparator) { protected void writeBuffer(char[] pBuffer, int pOffset, int pLen) throws IOException { pWriter.write(pBuffer, pOffset, pLen); } }; return new EncoderOutputStream(encoder); } /** An {@link Encoder}, which is writing to a SAX content handler. * This is typically used for embedding a base64 stream into an * XML document. */ public static class SAXEncoder extends Encoder { private final ContentHandler handler; /** Creates a new instance. * @param pBuffer The encoders buffer. * @param pWrapSize A nonzero value indicates, that a line * wrap should be performed after the given number of * characters. The value must be a multiple of 4. Zero * indicates, that no line wrap should be performed. * @param pSep The eol sequence being used to terminate * a line in case of line wraps. May be null, in which * case the default value {@link Base64#LINE_SEPARATOR} * is being used. * @param pHandler The target handler. */ public SAXEncoder(char[] pBuffer, int pWrapSize, String pSep, ContentHandler pHandler) { super(pBuffer, pWrapSize, pSep); handler = pHandler; } /** Writes to the content handler. * @throws SAXIOException Writing to the content handler * caused a SAXException. */ protected void writeBuffer(char[] pChars, int pOffset, int pLen) throws IOException { try { handler.characters(pChars, pOffset, pLen); } catch (SAXException e) { throw new SAXIOException(e); } } } /** Converts the given byte array into a base64 encoded character * array. * @param pBuffer The buffer being encoded. * @param pOffset Offset in buffer, where to begin encoding. * @param pLength Number of bytes being encoded. * @return Character array of encoded bytes. */ public static String encode(byte[] pBuffer, int pOffset, int pLength) { return encode(pBuffer, pOffset, pLength, LINE_SIZE, LINE_SEPARATOR); } /** Converts the given byte array into a base64 encoded character * array. * @param pBuffer The buffer being encoded. * @param pOffset Offset in buffer, where to begin encoding. * @param pLength Number of bytes being encoded. * @param pLineSize Size of one line in characters, must be a multiple * of four. Zero indicates, that no line wrapping should occur. * @param pSeparator Line separator or null, in which case the default value * {@link #LINE_SEPARATOR} is used. * @return Character array of encoded bytes. */ public static String encode(byte[] pBuffer, int pOffset, int pLength, int pLineSize, String pSeparator) { StringWriter sw = new StringWriter(); OutputStream ostream = newEncoder(sw, pLineSize, pSeparator); try { ostream.write(pBuffer, pOffset, pLength); ostream.close(); } catch (IOException e) { throw new UndeclaredThrowableException(e); } return sw.toString(); } /** Converts the given byte array into a base64 encoded character * array with the line size {@link #LINE_SIZE} and the separator * {@link #LINE_SEPARATOR}. * @param pBuffer The buffer being encoded. * @return Character array of encoded bytes. */ public static String encode(byte[] pBuffer) { return encode(pBuffer, 0, pBuffer.length); } /** An encoder is an object, which is able to decode char arrays * in blocks of four bytes. Any such block is converted into a * array of three bytes. */ public static abstract class Decoder { private final byte[] byteBuffer; private int byteBufferOffset; private int num, numBytes; private int eofBytes; /** Creates a new instance. * @param pBufLen The decoders buffer size. The decoder will * store up to this number of decoded bytes before invoking * {@link #writeBuffer(byte[],int,int)}. */ protected Decoder(int pBufLen) { byteBuffer = new byte[pBufLen]; } /** Called for writing the decoded bytes to the destination. * @param pBuffer The byte array being written. * @param pOffset Offset of the first byte being written. * @param pLen Number of bytes being written. * @throws IOException Writing to the destination failed. */ protected abstract void writeBuffer(byte[] pBuffer, int pOffset, int pLen) throws IOException; /** Converts the Base64 encoded character array. * @param pData The character array being decoded. * @param pOffset Offset of first character being decoded. * @param pLen Number of characters being decoded. * @throws DecodingException Decoding failed. * @throws IOException An invocation of the {@link #writeBuffer(byte[],int,int)} * method failed. */ public void write(char[] pData, int pOffset, int pLen) throws IOException { for (int i = 0; i < pLen; i++) { char c = pData[pOffset++]; if (Character.isWhitespace(c)) { continue; } if (c == '=') { ++eofBytes; num = num << 6; switch (++numBytes) { case 1: case 2: throw new DecodingException("Unexpected end of stream character (=)"); case 3: // Wait for the next '=' break; case 4: byteBuffer[byteBufferOffset++] = (byte) (num >> 16); if (eofBytes == 1) { byteBuffer[byteBufferOffset++] = (byte) (num >> 8); } writeBuffer(byteBuffer, 0, byteBufferOffset); byteBufferOffset = 0; break; case 5: throw new DecodingException("Trailing garbage detected"); default: throw new IllegalStateException("Invalid value for numBytes"); } } else { if (eofBytes > 0) { throw new DecodingException( "Base64 characters after end of stream character (=) detected."); } int result; if (c >= 0 && c < base64ToInt.length) { result = base64ToInt[c]; if (result >= 0) { num = (num << 6) + result; if (++numBytes == 4) { byteBuffer[byteBufferOffset++] = (byte) (num >> 16); byteBuffer[byteBufferOffset++] = (byte) ((num >> 8) & 0xff); byteBuffer[byteBufferOffset++] = (byte) (num & 0xff); if (byteBufferOffset + 3 > byteBuffer.length) { writeBuffer(byteBuffer, 0, byteBufferOffset); byteBufferOffset = 0; } num = 0; numBytes = 0; } continue; } } if (!Character.isWhitespace(c)) { throw new DecodingException("Invalid Base64 character: " + (int) c); } } } } /** Indicates, that no more data is being expected. Writes all currently * buffered data to the destination by invoking {@link #writeBuffer(byte[],int,int)}. * @throws DecodingException Decoding failed (Unexpected end of file). * @throws IOException An invocation of the {@link #writeBuffer(byte[],int,int)} method failed. */ public void flush() throws IOException { if (numBytes != 0 && numBytes != 4) { throw new DecodingException("Unexpected end of file"); } if (byteBufferOffset > 0) { writeBuffer(byteBuffer, 0, byteBufferOffset); byteBufferOffset = 0; } } } /** Returns a {@link Writer}, that decodes its Base64 encoded * input and writes it to the given {@link OutputStream}. * Note, that the writers {@link Writer#close()} method will * <em>not</em> close the output stream <code>pStream</code>! * @param pStream Target output stream. * @return An output stream, encoding its input in Base64 and writing * the output to the writer <code>pWriter</code>. */ public Writer newDecoder(final OutputStream pStream) { return new Writer() { private final Decoder decoder = new Decoder(1024) { protected void writeBuffer(byte[] pBytes, int pOffset, int pLen) throws IOException { pStream.write(pBytes, pOffset, pLen); } }; public void close() throws IOException { flush(); } public void flush() throws IOException { decoder.flush(); pStream.flush(); } public void write(char[] cbuf, int off, int len) throws IOException { decoder.write(cbuf, off, len); } }; } /** Converts the given base64 encoded character buffer into a byte array. * @param pBuffer The character buffer being decoded. * @param pOffset Offset of first character being decoded. * @param pLength Number of characters being decoded. * @return Converted byte array * @throws DecodingException The input character stream contained invalid data. */ public static byte[] decode(char[] pBuffer, int pOffset, int pLength) throws DecodingException { final ByteArrayOutputStream baos = new ByteArrayOutputStream(); Decoder d = new Decoder(1024) { protected void writeBuffer(byte[] pBuf, int pOff, int pLen) throws IOException { baos.write(pBuf, pOff, pLen); } }; try { d.write(pBuffer, pOffset, pLength); d.flush(); } catch (DecodingException e) { throw e; } catch (IOException e) { throw new UndeclaredThrowableException(e); } return baos.toByteArray(); } /** Converts the given base64 encoded character buffer into a byte array. * @param pBuffer The character buffer being decoded. * @return Converted byte array * @throws DecodingException The input character stream contained invalid data. */ public static byte[] decode(char[] pBuffer) throws DecodingException { return decode(pBuffer, 0, pBuffer.length); } /** Converts the given base64 encoded String into a byte array. * @param pBuffer The string being decoded. * @return Converted byte array * @throws DecodingException The input character stream contained invalid data. */ public static byte[] decode(String pBuffer) throws DecodingException { return decode(pBuffer.toCharArray()); } }