
org.jppf.utils.base64.Base64Encoding Maven / Gradle / Ivy
Show all versions of jppf-common Show documentation
/*
* JPPF.
* Copyright (C) 2005-2015 JPPF Team.
* http://www.jppf.org
*
* 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.
*/
package org.jppf.utils.base64;
import static org.jppf.utils.base64.Base64.*;
import java.io.*;
/**
*
* @author Laurent Cohen
*/
public final class Base64Encoding
{
/* ******** E N C O D I N G M E T H O D S ******** */
/**
* Encodes up to the first three bytes of array threeBytes and returns a four-byte array in Base64 notation.
* The actual number of significant bytes in your array is given by numSigBytes.
* The array threeBytes needs only be as big as numSigBytes.
* Code can reuse a byte array by passing a four-byte array as b4.
* @param b4 A reusable byte array to reduce array instantiation
* @param threeBytes the array to convert
* @param numSigBytes the number of significant bytes in your array
* @param options the options.
* @return four byte array in Base64 notation.
* @since 1.5.1
*/
static byte[] encode3to4( final byte[] b4, final byte[] threeBytes, final int numSigBytes, final int options ) {
encode3to4( threeBytes, 0, numSigBytes, b4, 0, options );
return b4;
} // end encode3to4
/**
* Encodes up to three bytes of the array source and writes the resulting four Base64 bytes to destination.
* The source and destination arrays can be manipulated anywhere along their length by specifying srcOffset and destOffset.
* This method does not check to make sure your arrays are large enough to accomodate srcOffset + 3 for
* the source array or destOffset + 4 for the destination array.
* The actual number of significant bytes in your array is given by numSigBytes.
*
This is the lowest level of the encoding methods with all possible parameters.
* @param source the array to convert
* @param srcOffset the index where conversion begins
* @param numSigBytes the number of significant bytes in your array
* @param destination the array to hold the conversion
* @param destOffset the index where output will be put
* @param options the options.
* @return the destination array
* @since 1.3
*/
static byte[] encode3to4(final byte[] source, final int srcOffset, final int numSigBytes, final byte[] destination, final int destOffset, final int options ) {
byte[] ALPHABET = getAlphabet( options );
// 1 2 3
// 01234567890123456789012345678901 Bit position
// --------000000001111111122222222 Array position from threeBytes
// --------| || || || | Six bit groups to index ALPHABET
// >>18 >>12 >> 6 >> 0 Right shift necessary
// 0x3f 0x3f 0x3f Additional AND
// Create buffer with zero-padding if there are only one or two
// significant bytes passed in the array.
// We have to shift left 24 in order to flush out the 1's that appear
// when Java treats a value as negative that is cast from a byte to an int.
int inBuff = ( numSigBytes > 0 ? ((source[ srcOffset ] << 24) >>> 8) : 0 )
| ( numSigBytes > 1 ? ((source[ srcOffset + 1 ] << 24) >>> 16) : 0 )
| ( numSigBytes > 2 ? ((source[ srcOffset + 2 ] << 24) >>> 24) : 0 );
switch( numSigBytes )
{
case 3:
destination[ destOffset ] = ALPHABET[ (inBuff >>> 18) ];
destination[ destOffset + 1 ] = ALPHABET[ (inBuff >>> 12) & 0x3f ];
destination[ destOffset + 2 ] = ALPHABET[ (inBuff >>> 6) & 0x3f ];
destination[ destOffset + 3 ] = ALPHABET[ (inBuff ) & 0x3f ];
return destination;
case 2:
destination[ destOffset ] = ALPHABET[ (inBuff >>> 18) ];
destination[ destOffset + 1 ] = ALPHABET[ (inBuff >>> 12) & 0x3f ];
destination[ destOffset + 2 ] = ALPHABET[ (inBuff >>> 6) & 0x3f ];
destination[ destOffset + 3 ] = EQUALS_SIGN;
return destination;
case 1:
destination[ destOffset ] = ALPHABET[ (inBuff >>> 18) ];
destination[ destOffset + 1 ] = ALPHABET[ (inBuff >>> 12) & 0x3f ];
destination[ destOffset + 2 ] = EQUALS_SIGN;
destination[ destOffset + 3 ] = EQUALS_SIGN;
return destination;
default:
return destination;
} // end switch
} // end encode3to4
/**
* Performs Base64 encoding on the raw
ByteBuffer, writing it to the encoded
ByteBuffer.
* This is an experimental feature. Currently it does not pass along any options (such as {@link Base64#DO_BREAK_LINES DO_BREAK_LINES} or {@link Base64#GZIP GZIP}.
* @param raw input buffer
* @param encoded output buffer
* @since 2.3
*/
public static void encode( final java.nio.ByteBuffer raw, final java.nio.ByteBuffer encoded ){
byte[] raw3 = new byte[3];
byte[] enc4 = new byte[4];
while( raw.hasRemaining() ){
int rem = Math.min(3,raw.remaining());
raw.get(raw3,0,rem);
encode3to4(enc4, raw3, rem, Base64.NO_OPTIONS );
encoded.put(enc4);
} // end input remaining
}
/**
* Performs Base64 encoding on the raw
ByteBuffer, writing it to the encoded
CharBuffer.
* This is an experimental feature. Currently it does not pass along any options (such as {@link Base64#DO_BREAK_LINES DO_BREAK_LINES} or {@link Base64#GZIP GZIP}.
* @param raw input buffer
* @param encoded output buffer
* @since 2.3
*/
public static void encode( final java.nio.ByteBuffer raw, final java.nio.CharBuffer encoded ){
byte[] raw3 = new byte[3];
byte[] enc4 = new byte[4];
while( raw.hasRemaining() ){
int rem = Math.min(3,raw.remaining());
raw.get(raw3,0,rem);
encode3to4(enc4, raw3, rem, Base64.NO_OPTIONS );
for( int i = 0; i < 4; i++ ) encoded.put( (char)(enc4[i] & 0xFF) );
} // end input remaining
}
/**
* Serializes an object and returns the Base64-encoded version of that serialized object.
*
As of v 2.3, if the object cannot be serialized or there is another error,
* the method will throw an IOException. This is new to v2.3!
* In earlier versions, it just returned a null value, but in retrospect that's a pretty poor way to handle it.
* The object is not GZip-compressed before being encoded.
* @param serializableObject The object to encode
* @return The Base64-encoded object
* @throws IOException if there is an error
* @throws NullPointerException if serializedObject is null
* @since 1.4
*/
public static String encodeObject( final Serializable serializableObject ) throws IOException {
return encodeObject( serializableObject, NO_OPTIONS );
} // end encodeObject
/**
* Serializes an object and returns the Base64-encoded version of that serialized object.
* As of v 2.3, if the object cannot be serialized or there is another error,
* the method will throw an IOException. This is new to v2.3!
* In earlier versions, it just returned a null value, but in retrospect that's a pretty poor way to handle it.
* The object is not GZip-compressed before being encoded.
* Example options:
* GZIP: gzip-compresses object before encoding it.
* DO_BREAK_LINES: break lines at 76 characters
*
* Example: encodeObject( myObj, Base64.GZIP )
or
*
Example: encodeObject( myObj, Base64.GZIP | Base64.DO_BREAK_LINES )
* @param serializableObject The object to encode
* @param options Specified options
* @return The Base64-encoded object
* @see Base64#GZIP
* @see Base64#DO_BREAK_LINES
* @throws IOException if there is an error
* @since 2.0
*/
public static String encodeObject( final Serializable serializableObject, final int options ) throws IOException {
if( serializableObject == null ){
throw new NullPointerException( "Cannot serialize a null object." );
} // end if: null
// Streams
ByteArrayOutputStream baos = null;
OutputStream b64os = null;
java.util.zip.GZIPOutputStream gzos = null;
ObjectOutputStream oos = null;
try {
// ObjectOutputStream -> (GZIP) -> Base64 -> ByteArrayOutputStream
baos = new ByteArrayOutputStream();
b64os = new Base64OutputStream( baos, ENCODE | options );
if( (options & GZIP) != 0 ){
// Gzip
gzos = new java.util.zip.GZIPOutputStream(b64os);
oos = new ObjectOutputStream( gzos );
} else {
// Not gzipped
oos = new ObjectOutputStream( b64os );
}
oos.writeObject( serializableObject );
} // end try
catch( IOException e ) {
// Catch it and then throw it immediately so that
// the finally{} block is called for cleanup.
throw e;
} // end catch
finally {
try{ oos.close(); } catch( Exception e ){}
try{ gzos.close(); } catch( Exception e ){}
try{ b64os.close(); } catch( Exception e ){}
try{ baos.close(); } catch( Exception e ){}
} // end finally
// Return value according to relevant encoding.
try {
return new String( baos.toByteArray(), PREFERRED_ENCODING );
} // end try
catch (UnsupportedEncodingException uue){
// Fall back to some Java default
return new String( baos.toByteArray() );
} // end catch
} // end encode
/**
* Encodes a byte array into Base64 notation. Does not GZip-compress data.
* @param source The data to convert
* @return The data in Base64-encoded form
* @throws NullPointerException if source array is null
* @since 1.4
*/
public static String encodeBytes( final byte[] source ) {
// Since we're not going to have the GZIP encoding turned on, we're not going to have an IOException thrown, so
// we should not force the user to have to catch it.
String encoded = null;
try {
encoded = encodeBytes(source, 0, source.length, NO_OPTIONS);
} catch (IOException ex) {
assert false : ex.getMessage();
} // end catch
assert encoded != null;
return encoded;
} // end encodeBytes
/**
* Encodes a byte array into Base64 notation.
*
Example options:
* GZIP: gzip-compresses object before encoding it.
* DO_BREAK_LINES: break lines at 76 characters
* Note: Technically, this makes your encoding non-compliant.
*
* Example: encodeBytes( myData, Base64.GZIP )
or
*
Example: encodeBytes( myData, Base64.GZIP | Base64.DO_BREAK_LINES )
*
As of v 2.3, if there is an error with the GZIP stream, the method will throw an IOException. This is new to v2.3!
* In earlier versions, it just returned a null value, but in retrospect that's a pretty poor way to handle it.
* @param source The data to convert
* @param options Specified options
* @return The Base64-encoded data as a String
* @see Base64#GZIP
* @see Base64#DO_BREAK_LINES
* @throws IOException if there is an error
* @throws NullPointerException if source array is null
* @since 2.0
*/
public static String encodeBytes( final byte[] source, final int options ) throws IOException {
return encodeBytes( source, 0, source.length, options );
} // end encodeBytes
/**
* Encodes a byte array into Base64 notation. Does not GZip-compress data.
* As of v 2.3, if there is an error, the method will throw an IOException. This is new to v2.3!
* In earlier versions, it just returned a null value, but in retrospect that's a pretty poor way to handle it.
* @param source The data to convert
* @param off Offset in array where conversion should begin
* @param len Length of data to convert
* @return The Base64-encoded data as a String
* @throws NullPointerException if source array is null
* @throws IllegalArgumentException if source array, offset, or length are invalid
* @since 1.4
*/
public static String encodeBytes( final byte[] source, final int off, final int len ) {
// Since we're not going to have the GZIP encoding turned on, we're not going to have an IOException thrown, so
// we should not force the user to have to catch it.
String encoded = null;
try {
encoded = encodeBytes( source, off, len, NO_OPTIONS );
} catch (IOException ex) {
assert false : ex.getMessage();
} // end catch
assert encoded != null;
return encoded;
} // end encodeBytes
/**
* Encodes a byte array into Base64 notation.
* Example options:
* GZIP: gzip-compresses object before encoding it.
* DO_BREAK_LINES: break lines at 76 characters
* Note: Technically, this makes your encoding non-compliant.
*
* Example: encodeBytes( myData, Base64.GZIP )
or
*
Example: encodeBytes( myData, Base64.GZIP | Base64.DO_BREAK_LINES )
*
As of v 2.3, if there is an error with the GZIP stream, the method will throw an IOException. This is new to v2.3!
* In earlier versions, it just returned a null value, but in retrospect that's a pretty poor way to handle it.
* @param source The data to convert
* @param off Offset in array where conversion should begin
* @param len Length of data to convert
* @param options Specified options
* @return The Base64-encoded data as a String
* @see Base64#GZIP
* @see Base64#DO_BREAK_LINES
* @throws IOException if there is an error
* @throws NullPointerException if source array is null
* @throws IllegalArgumentException if source array, offset, or length are invalid
* @since 2.0
*/
public static String encodeBytes( final byte[] source, final int off, final int len, final int options ) throws IOException {
byte[] encoded = encodeBytesToBytes( source, off, len, options );
// Return value according to relevant encoding.
try {
return new String( encoded, PREFERRED_ENCODING );
} // end try
catch (UnsupportedEncodingException uue) {
return new String( encoded );
} // end catch
} // end encodeBytes
/**
* Similar to {@link #encodeBytes(byte[])} but returnsa byte array instead of instantiating a String.
* This is more efficient if you're working with I/O streams and have large data sets to encode.
* @param source The data to convert
* @return The Base64-encoded data as a byte[] (of ASCII characters)
* @throws NullPointerException if source array is null
* @since 2.3.1
*/
public static byte[] encodeBytesToBytes( final byte[] source ) {
byte[] encoded = null;
try {
encoded = encodeBytesToBytes( source, 0, source.length, Base64.NO_OPTIONS );
} catch( IOException ex ) {
assert false : "IOExceptions only come from GZipping, which is turned off: " + ex.getMessage();
}
return encoded;
}
/**
* Similar to {@link #encodeBytes(byte[], int, int, int)} but returns a byte array instead of instantiating a String.
* This is more efficient if you're working with I/O streams and have large data sets to encode.
* @param source The data to convert
* @param off Offset in array where conversion should begin
* @param len Length of data to convert
* @param options Specified options
* @return The Base64-encoded data as a String
* @see Base64#GZIP
* @see Base64#DO_BREAK_LINES
* @throws IOException if there is an error
* @throws NullPointerException if source array is null
* @throws IllegalArgumentException if source array, offset, or length are invalid
* @since 2.3.1
*/
public static byte[] encodeBytesToBytes( final byte[] source, final int off, final int len, final int options ) throws IOException {
if( source == null ) throw new NullPointerException( "Cannot serialize a null array." );
if( off < 0 ) throw new IllegalArgumentException( "Cannot have negative offset: " + off );
if( len < 0 ) throw new IllegalArgumentException( "Cannot have length offset: " + len );
if( off + len > source.length ) throw new IllegalArgumentException(String.format( "Cannot have offset of %d and length of %d with array of length %d", off,len,source.length));
// Compress?
if( (options & GZIP) != 0 ) {
ByteArrayOutputStream baos = null;
java.util.zip.GZIPOutputStream gzos = null;
Base64OutputStream b64os = null;
try {
// GZip -> Base64 -> ByteArray
baos = new ByteArrayOutputStream();
b64os = new Base64OutputStream( baos, ENCODE | options );
gzos = new java.util.zip.GZIPOutputStream( b64os );
gzos.write( source, off, len );
gzos.close();
} // end try
catch( IOException e ) {
// Catch it and then throw it immediately so that the finally{} block is called for cleanup.
throw e;
} // end catch
finally {
try{ gzos.close(); } catch( Exception e ){}
try{ b64os.close(); } catch( Exception e ){}
try{ baos.close(); } catch( Exception e ){}
} // end finally
return baos.toByteArray();
} // end if: compress
// Else, don't compress. Better not to use streams at all then.
else {
boolean breakLines = (options & DO_BREAK_LINES) != 0;
//int len43 = len * 4 / 3;
//byte[] outBuff = new byte[ ( len43 ) // Main 4:3
// + ( (len % 3) > 0 ? 4 : 0 ) // Account for padding
// + (breakLines ? ( len43 / MAX_LINE_LENGTH ) : 0) ]; // New lines
// Try to determine more precisely how big the array needs to be.
// If we get it right, we don't have to do an array copy, and we save a bunch of memory.
int encLen = ( len / 3 ) * 4 + ( len % 3 > 0 ? 4 : 0 ); // Bytes needed for actual encoding
if( breakLines ){
encLen += encLen / MAX_LINE_LENGTH; // Plus extra newline characters
}
byte[] outBuff = new byte[ encLen ];
int d = 0;
int e = 0;
int len2 = len - 2;
int lineLength = 0;
for( ; d < len2; d+=3, e+=4 ) {
encode3to4( source, d+off, 3, outBuff, e, options );
lineLength += 4;
if( breakLines && lineLength >= MAX_LINE_LENGTH )
{
outBuff[e+4] = NEW_LINE;
e++;
lineLength = 0;
} // end if: end of line
} // en dfor: each piece of array
if( d < len ) {
encode3to4( source, d+off, len - d, outBuff, e, options );
e += 4;
} // end if: some padding needed
// Only resize array if we didn't guess it right.
if( e <= outBuff.length - 1 ){
// If breaking lines and the last byte falls right at the line length (76 bytes per line), there will be
// one extra byte, and the array will need to be resized. Not too bad of an estimate on array size, I'd say.
byte[] finalOut = new byte[e];
System.arraycopy(outBuff,0, finalOut,0,e);
//System.err.println("Having to resize array from " + outBuff.length + " to " + e );
return finalOut;
}
else return outBuff;
} // end else: don't compress
} // end encodeBytesToBytes
}