src.com.ibm.as400.access.DataStreamCompression Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jt400-jdk8 Show documentation
Show all versions of jt400-jdk8 Show documentation
The Open Source version of the IBM Toolbox for Java
///////////////////////////////////////////////////////////////////////////////
//
// JTOpen (IBM Toolbox for Java - OSS version)
//
// Filename: DataStreamCompression.java
//
// The source code contained herein is licensed under the IBM Public License
// Version 1.0, which has been approved by the Open Source Initiative.
// Copyright (C) 1997-2001 International Business Machines Corporation and
// others. All rights reserved.
//
///////////////////////////////////////////////////////////////////////////////
package com.ibm.as400.access;
/**
The DataStreamCompression class provides support for RLE (Run Length Encoding)
compression and decompression. When data is compressed using RLE, contiguous
repeating bytes in the datastream are replaced with an RLE record. When data
is decompressed, the RLE record is expanded out into repeating bytes again.
The format of the output RLE record is as follows:
- RLE repeater record:
- 1 byte escape
- 2 byte repeater
- 2 byte repeat count
- RLE escape record:
- 1 byte escape
- 1 byte escape
During compression, each escape byte will be replaced with an RLE escape
record (two escape bytes). If a byte is not an escape byte, then two bytes
are compared to the next two bytes to determine if the byte pair is repeated.
Compression is done and an RLE repeater record built if the byte pairs are
repeated more than five times (totaling 10 or more bytes repeated).
For additional information on RLE, see the LIPI documentation for the
Remote Command/Program Server.
**/
class DataStreamCompression
{
private static final String copyright = "Copyright (C) 1997-2001 International Business Machines Corporation and others.";
final static byte DEFAULT_ESCAPE = (byte) 0x1B;
final static int ESCAPE_SIZE = 1;
final static int REPEATER_SIZE = 2;
final static int COUNT_SIZE = 2;
final static int REPEATER_RECORD_SIZE = ESCAPE_SIZE + REPEATER_SIZE + COUNT_SIZE;
final static int ESCAPE_RECORD_SIZE = ESCAPE_SIZE + ESCAPE_SIZE;
/**
No need to instantiate an object of this class since the class
only contains two static methods. Emphasizing this fact with a
private ctor.
**/
private DataStreamCompression ()
{
}
/**
Compress data in the source byte array and write the compressed data
to the returned byte array.
@param source The source (decompressed) bytes.
@param sourceOffset The offset in the source bytes at which to start
compressing.
@param length The length of the bytes to compress.
@param escape The escape character. Use DEFAULT_ESCAPE.
@return The compressed bytes or null if the data was not compressed.
If the compressed data length is larger than the uncompressed
length, null is returned.
**/
static byte[] compressRLE (byte[] source,
int sourceOffset,
int length,
byte escape)
{
// Validate the input byte array.
if (source == null)
{
throw new NullPointerException("source");
}
// Validate the source offset value.
if (sourceOffset >= source.length)
{
throw new ExtendedIllegalArgumentException("sourceOffset", ExtendedIllegalArgumentException.PARAMETER_VALUE_NOT_VALID);
}
// Validate the length value.
if (length <= 0)
{
throw new ExtendedIllegalArgumentException("length", ExtendedIllegalArgumentException.PARAMETER_VALUE_NOT_VALID);
}
// Set flags to indicate which type of tracing, if any, should be done.
boolean traceDiagnostic = Trace.isTraceOn() && Trace.isTraceDiagnosticOn();
byte[] destination = new byte[length];
int compressedCount = compressRLEInternal(source, sourceOffset, length, destination, 0, escape); // @A1A
if (compressedCount >= 0)
{ // @A1A
byte[] returnBytes = new byte[compressedCount]; // @A1A
System.arraycopy(destination, 0, returnBytes, 0, compressedCount); // @A1A
return returnBytes; // @A1A
} // @A1A
else // @A1A
return null; // @A1A
}
// @A1A
/**
Compress data in the source byte array and write the compressed data
to the destination byte array.
@param source The source (decompressed) bytes.
@param sourceOffset The offset in the source bytes at which to start
compressing.
@param length The length of the bytes to compress.
@param destination The destination (compressed) bytes.
@param destinationOffset The offset in the destination bytes at which to
assign compressed bytes.
@param escape The escape character. Use DEFAULT_ESCAPE.
@return The number of compressed bytes, or -1 if the data was not compressed.
If the compressed data length is larger than the uncompressed length,
null is returned.
**/
static int compressRLE (byte[] source,
int sourceOffset,
int length,
byte[] destination,
int destinationOffset,
byte escape)
{
// Validate the input byte array.
if (source == null)
{
throw new NullPointerException("source");
}
// Validate the source offset value.
if (sourceOffset >= source.length)
{
throw new ExtendedIllegalArgumentException("sourceOffset", ExtendedIllegalArgumentException.PARAMETER_VALUE_NOT_VALID);
}
// Validate the length value.
if (length <= 0)
{
throw new ExtendedIllegalArgumentException("length", ExtendedIllegalArgumentException.PARAMETER_VALUE_NOT_VALID);
}
// Validate the input byte array.
if (destination == null)
{
throw new NullPointerException("destinationOffset");
}
// Validate the source offset value.
if (destinationOffset >= destination.length)
{
throw new ExtendedIllegalArgumentException("destinationOffset", ExtendedIllegalArgumentException.PARAMETER_VALUE_NOT_VALID);
}
return compressRLEInternal(source, sourceOffset, length, destination, destinationOffset, escape);
}
// @A1A - Moved from compressRLE(byte[], int, int, byte)
/**
Compress data in the source byte array and write the compressed data
to the destination byte array.
@param source The source (decompressed) bytes.
@param sourceOffset The offset in the source bytes at which to start
compressing.
@param length The length of the bytes to compress.
@param destination The destination (compressed) bytes.
@param destinationOffset The offset in the destination bytes at which to
assign compressed bytes.
@param escape The escape character. Use DEFAULT_ESCAPE.
@return The number of compressed bytes, or -1 if the data was not compressed.
If the compressed data length is larger than the uncompressed length,
null is returned.
**/
private static int compressRLEInternal (byte[] source,
int sourceOffset,
int length,
byte[] destination,
int destinationOffset,
byte escape)
{
// Set flags to indicate which type of tracing, if any, should be done.
boolean traceDiagnostic = Trace.isTraceOn() && Trace.isTraceDiagnosticOn();
int returnCount = -1;
int i = sourceOffset; // Index into source.
int j = destinationOffset; // Index into destination.
boolean overflow = false; // destination array overflow indicator
int sourceLength = sourceOffset + length;
int destinationLength = destination.length;
if (traceDiagnostic)
{
Trace.log(Trace.DIAGNOSTIC, "compressRLE() sourceLength: " + sourceLength);
}
while (i < sourceLength && overflow != true)
{
// Have an escape byte
if (source[i] == escape)
{
// Bytes fit in destination array
if (j + ESCAPE_RECORD_SIZE <= destinationLength)
{
// Write out an escape record
destination[j++] = escape;
destination[j++] = escape;
++i;
}
// Destination array overflow
else
{
if (traceDiagnostic)
Trace.log(Trace.DIAGNOSTIC, "Overflow when writing out escape record starting at dest " + j + " ...");
overflow = true;
}
}
// Have a single, non-escape byte and end of source data.
else if ((i+1) >= sourceLength)
{
// Bytes fit in destination array
if (j < destinationLength)
{
// Write out the last byte.
destination[j++] = source[i++];
}
// Destination array overflow
else
{
if (traceDiagnostic)
Trace.log(Trace.DIAGNOSTIC, "Overflow when writing out last byte before EOD to dest " + j + " ...");
overflow = true;
}
}
// Have a single byte, followed by an escape character.
else if (source[i+1] == escape)
{
// Bytes fit in destination array
if ((j + 1 + ESCAPE_RECORD_SIZE) <= destinationLength)
{
// Write out the single byte and then the escape record.
destination[j++] = source[i++]; // byte before the escape byte
destination[j++] = escape;
destination[j++] = escape;
++i; // byte after the escape byte
}
// Destination array overflow
else
{
if (traceDiagnostic)
Trace.log(Trace.DIAGNOSTIC, "Overflow when writing out single byte and escape record starting at dest " + j + " ...");
overflow = true;
}
}
// Have at least two non-escape bytes that could be a repeater.
// Compare two bytes with next two bytes.
else
{
int saveOffset = i;
//@P0D int repeater = BinaryConverter.byteArrayToUnsignedShort(source, i); // @A2C
// int repeater = ((source[i] & 0xFF) << 8) + (source[i+1] & 0xFF); //@P0A
// @M7C
byte repeaterByte1 = source[i];
byte repeaterByte2 = source[i+1];
int count = 1; // @A2C
i += 2;
// Calculate the number of times these two bytes are repeated.
// while (((i+1) < sourceLength) && repeater == (((source[i] & 0xFF) << 8) + (source[i+1] & 0xFF)) && count < 65535) //@P0C @B1A
while (((i+1) < sourceLength) && repeaterByte1 == source[i] && repeaterByte2 == source[i+1] && count < 65535) //@P0C @B1A
{ // @A2C
count++;
i += 2;
}
// Calculate the length of the repeating characters.
int repeatLength = count * REPEATER_SIZE;
// Determine if we have enough repeating bytes to merit an RLE record.
if (repeatLength >= REPEATER_RECORD_SIZE * 2)
{
// Enough repeating data. Build RLE record.
if (j + REPEATER_RECORD_SIZE <= destinationLength)
{
// Bytes fit in destination array; write out the repeated bytes.
destination[j] = escape; //@P0C
//@P0D BinaryConverter.unsignedShortToByteArray(repeater, destination, j); // @A2C
// destination[++j] = (byte)(repeater >>> 8); //@P0A
// destination[++j] = (byte) repeater; //@P0A
destination[++j] = repeaterByte1;
destination[++j] = repeaterByte2;
//@P0D BinaryConverter.unsignedShortToByteArray(count, destination, j+2); // @A2C
//@P0D j += 4;
destination[++j] = (byte)(count >>> 8); //@P0A
destination[++j] = (byte) count; //@P0A
++j; //@P0A
}
// Destination array overflow
else
{
if (traceDiagnostic)
Trace.log(Trace.DIAGNOSTIC, "Overflow when writing out RLE repeater record starting at dest " + j + " ...");
overflow = true;
}
}
else
{
// Not enough repeating data. Just copy the data to destination array.
i = saveOffset;
// Bytes fit in destination array
if ((j + (repeatLength - 1)) < destinationLength)
{
// Write out the repeated bytes.
for (int n=0; n < repeatLength; n++)
{
destination[j++] = source[i++];
}
}
// Destination array overflow
else
{
if (traceDiagnostic)
Trace.log(Trace.DIAGNOSTIC, "Overflow when writing out non-repeating bytes to dest " + j + " ...");
overflow = true;
}
}
}
}
returnCount = j - destinationOffset; // @A1A
if (!overflow && (returnCount < length))
{ // @A1C
if (traceDiagnostic)
Trace.log(Trace.DIAGNOSTIC, "compressRLE() length of compressed bytes returned: " + j);
}
else
{
returnCount = -1;
if (traceDiagnostic)
Trace.log(Trace.DIAGNOSTIC, "compressRLE() returning null (compressed size >= decompressed size)");
}
return returnCount;
}
/**
Decompress data in the source byte array and write the decompressed data
to the returned byte array.
@param source The source (compressed) bytes.
@param sourceOffset The offset in the source bytes at which to start
decompressing.
@param length The length of the bytes to decompress.
@param decompressedLength The length of the bytes in their decompressed state.
If this length is provided, the byte array to be
returned will be created using this length. If this
length is not known, input 0. When set to 0, this
value will be calculated and the returned byte array
will be created with the calculated length.
Performance is improved when the correct decompressed
length is provided as input.
@param escape The escape character. Use DEFAULT_ESCAPE.
@return The decompressed bytes.
**/
static byte[] decompressRLE (byte[] source,
int sourceOffset,
int length,
int decompressedLength,
byte escape)
{
// Validate the input byte array.
if (source == null)
{
throw new NullPointerException("source");
}
// Validate the source offset value.
if (sourceOffset >= source.length)
{
throw new ExtendedIllegalArgumentException("sourceOffset", ExtendedIllegalArgumentException.PARAMETER_VALUE_NOT_VALID);
}
// Validate the length value.
if (length <= 0)
{
throw new ExtendedIllegalArgumentException("length", ExtendedIllegalArgumentException.PARAMETER_VALUE_NOT_VALID);
}
// Validate the decompressed length value.
if (decompressedLength < 0)
{
throw new ExtendedIllegalArgumentException("decompressedLength", ExtendedIllegalArgumentException.PARAMETER_VALUE_NOT_VALID);
}
// Create a temporary buffer for the decompressed data.
byte[] destination;
if (decompressedLength == 0)
{
destination = new byte[2048];
}
else
{
destination = new byte[decompressedLength];
}
return decompressRLEInternal(source, sourceOffset, length, destination, 0, escape, true, true); // @A1A
}
// @A1A
/**
Decompress data in the source byte array and write the decompressed data
to the destination byte array.
@param source The source (compressed) bytes.
@param sourceOffset The offset in the source bytes at which to start
decompressing.
@param length The length of the bytes to decompress.
@param destination The destination (decompressed) bytes.
@param destinationOffset The offset in the destination bytes at which to
assign decompressed bytes.
@param escape The escape character. Use DEFAULT_ESCAPE.
@param emptyDestination If set to true, assumes that the destination contains only
zeros and the decompression can be optimized
**/
static void decompressRLE (byte[] source,
int sourceOffset,
int length,
byte[] destination,
int destinationOffset,
byte escape,
boolean emptyDestination)
{
// Validate the input byte array.
if (source == null)
{
throw new NullPointerException("source");
}
// Validate the source offset value.
if (sourceOffset >= source.length)
{
throw new ExtendedIllegalArgumentException("sourceOffset", ExtendedIllegalArgumentException.PARAMETER_VALUE_NOT_VALID);
}
// Validate the length value.
if (length <= 0)
{
throw new ExtendedIllegalArgumentException("length", ExtendedIllegalArgumentException.PARAMETER_VALUE_NOT_VALID);
}
// Validate the input byte array.
if (destination == null)
{
throw new NullPointerException("destination");
}
// Validate the source offset value.
if (destinationOffset >= destination.length)
{
throw new ExtendedIllegalArgumentException("destinationOffset", ExtendedIllegalArgumentException.PARAMETER_VALUE_NOT_VALID);
}
decompressRLEInternal(source, sourceOffset, length, destination, destinationOffset, escape, false, emptyDestination);
}
// @A1A - Moved from decompressRLE(byte[], int, int, int, byte)
/**
Decompress data in the source byte array and write the decompressed data
to the destination byte array.
@param source The source (compressed) bytes.
@param sourceOffset The offset in the source bytes at which to start
decompressing.
@param length The length of the bytes to decompress.
@param destination The destination (decompressed) bytes.
@param destinationOffset The offset in the destination bytes at which to
assign decompressed bytes.
@param escape The escape character. Use DEFAULT_ESCAPE.
@param reallocate true to reallocate the destination array, if needed,
false otherwise.
@parm emptyDestination true if the destination is initially empty
This is is the case, then expansion of 0 bytes can be
optimized.
@return The decompressed bytes.
**/
private static byte[] decompressRLEInternal (byte[] source,
int sourceOffset,
int length,
byte[] destination,
int destinationOffset,
byte escape,
boolean reallocate,
boolean emptyDestination)
{
// Set flags to indicate which type of tracing, if any, should be done.
boolean traceDiagnostic = Trace.isTraceOn() && Trace.isTraceDiagnosticOn();
boolean traceError = Trace.isTraceOn() && Trace.isTraceErrorOn();
int i = sourceOffset; // Index into source.
int j = destinationOffset; // Index into destination.
int saveI = -1; // keep current position of the source index
int saveJ = -1; // keep current position of the destination index
boolean overflow = false; // destination array overflow indicator
int bytesNeeded = 0; // number of additional bytes needed
int sourceLength = sourceOffset + length;
int destinationLength = destination.length;
if (traceDiagnostic)
{
Trace.log(Trace.DIAGNOSTIC, "decompressRLE() sourceLength: " + sourceLength);
Trace.log(Trace.DIAGNOSTIC, "decompressRLE() destinationLength: " + destinationLength);
}
while (i < sourceLength)
{
// Have an escape byte
if (source[i] == escape)
{
// Not end of source
if ((i + ESCAPE_SIZE) < sourceLength)
{
// Second byte is escape; have an RLE escape record
if (source[i + ESCAPE_SIZE] == escape)
{
// Byte fits in destination array
if (j < destinationLength)
{
// Add escape byte to destination array
destination[j] = escape;
}
// Destination array overflow
else
{
if (traceDiagnostic)
{
Trace.log(Trace.DIAGNOSTIC, "Overflow while decompressing RLE escape record starting at " + j + " ...");
}
// save each index only when overflow is encountered
// the first time; otherwise, just tally up the number of
// additional bytes needed
if (!overflow)
{
saveI = i;
saveJ = j;
}
overflow = true;
bytesNeeded += ESCAPE_SIZE;
}
i += ESCAPE_RECORD_SIZE;
j += ESCAPE_SIZE;
}
// Should have an RLE repeater record;
// have an escape byte followed by a non-escape byte
else
{
// Not end of source; have a complete RLE repeater record
if ((i + REPEATER_SIZE + COUNT_SIZE) < sourceLength)
{
// Get repeater
//@P0D int repeater = BinaryConverter.byteArrayToUnsignedShort(source, i + ESCAPE_SIZE); // @A2C
// int repeater = ((source[i+ESCAPE_SIZE] & 0xFF) << 8) + (source[i+ESCAPE_SIZE+1] & 0xFF); //@P0A
//@M7C
byte repeatByte1 = source[i+ESCAPE_SIZE];
byte repeatByte2 = source[i+ESCAPE_SIZE+1];
// Get repeat count
//@P0D int count = BinaryConverter.byteArrayToUnsignedShort(source, i + ESCAPE_SIZE + REPEATER_SIZE); // @A2C
int count = ((source[i+ESCAPE_SIZE+REPEATER_SIZE] & 0xFF) << 8) + (source[i+ESCAPE_SIZE+REPEATER_SIZE+1] & 0xFF); //@P0A
// Bytes fit in destination array
if ((j + (count * REPEATER_SIZE)) <= destinationLength)
{
// Check to see if we can skip copying the zero'd bytes if the
// destination is still empty. @M7A
if (repeatByte1 == 0 && repeatByte2 == 0 && emptyDestination ) {
j += count * REPEATER_SIZE;
} else {
// Write out the bytes to destination array
for (int k = 1; k <= count; ++k)
{ // @A2C
//@P0D BinaryConverter.unsignedShortToByteArray(repeater, destination, j); // @A2C
// destination[j] = (byte)(repeater >>> 8); //@P0A
// destination[j+1] = (byte) repeater; //@P0A
//@M7C
destination[j] = repeatByte1;
destination[j+1] = repeatByte2; //@P0A
j += REPEATER_SIZE;
}
}
}
// Destination array overflow
else
{
if (traceDiagnostic)
{
Trace.log(Trace.DIAGNOSTIC, "Overflow while decompressing RLE repeater record starting at dest " + j + " ...");
}
// save each index only when overflow is encountered
// the first time; otherwise, just tally up the number of
// additional bytes needed
if (!overflow)
{
saveI = i;
saveJ = j;
}
overflow = true;
bytesNeeded += (count * REPEATER_SIZE);
j += (count * REPEATER_SIZE);
}
i += REPEATER_RECORD_SIZE;
}
// Error (don't have a complete RLE repeater record before EOD)
else
{
if (traceError)
Trace.log(Trace.ERROR, "Don't have a complete RLE repeater record before EOD ...");
throw new InternalErrorException(InternalErrorException.SYNTAX_ERROR );
}
}
}
// End of source reached and have single escape byte
else
{
if (traceError)
Trace.log(Trace.ERROR, "Don't have a complete RLE escape record before EOD ...");
throw new InternalErrorException(InternalErrorException.SYNTAX_ERROR );
}
}
// No RLE record found; just copy bytes from source to destination arrray
else
{
// Byte fits in destination array
if (j < destinationLength)
{
// Add byte to destination array
destination[j++] = source[i++];
}
// Destination array overflow
else
{
if (traceDiagnostic)
{
Trace.log(Trace.DIAGNOSTIC, "Overflow when writing out single bytes ...");
}
// save each index only when overflow is encountered
// the first time; otherwise, just tally up the number of
// additional bytes needed
if (!overflow)
{
saveI = i;
saveJ = j;
}
overflow = true;
bytesNeeded += 1;
++i;
++j;
}
}
}
// Buffer for decompressed bytes that will be returned.
byte[] returnBytes;
if ((overflow) && (reallocate))
{
// Destination array too small. Add bytes needed to length
// and create a return buffer of the correct length.
j = saveJ;
returnBytes = new byte[(j + bytesNeeded)];
int returnBytesLength = returnBytes.length;
if (traceDiagnostic)
{
Trace.log(Trace.DIAGNOSTIC, "Overflow. Size updated to " + returnBytesLength + " bytes.");
}
System.arraycopy(destination, 0, returnBytes, 0, j);
i = saveI;
overflow = false;
while (i < sourceLength)
{
// Have an escape byte
if (source[i] == escape)
{
// Not end of source
if ((i + ESCAPE_SIZE) < sourceLength)
{
// Second byte is escape; have an RLE escape record
if (source[i + ESCAPE_SIZE] == escape)
{
// Add escape byte to destination array
returnBytes[j++] = escape;
i += ESCAPE_RECORD_SIZE;
}
// Should have an RLE repeater record;
// have an escape byte followed by a non-escape byte
else
{
// Not end of source; have a complete RLE repeater record
if ((i + REPEATER_SIZE + COUNT_SIZE) < sourceLength)
{
// Get repeater
//@P0D int repeater = BinaryConverter.byteArrayToUnsignedShort(source, i + ESCAPE_SIZE); // @A2C
int repeater = ((source[i+ESCAPE_SIZE] & 0xFF) << 8) + (source[i+ESCAPE_SIZE+1] & 0xFF); //@P0A
// Get repeat count
//@P0D int count = BinaryConverter.byteArrayToUnsignedShort(source, i + ESCAPE_SIZE + REPEATER_SIZE); // @A2C
int count = ((source[i+ESCAPE_SIZE+REPEATER_SIZE] & 0xFF) << 8) + (source[i+ESCAPE_SIZE+REPEATER_SIZE+1] & 0xFF); //@P0A
// Write out the bytes to destination array
for (int k = 1; k <= count; k++)
{ // @A2C
//@P0D BinaryConverter.unsignedShortToByteArray(repeater, returnBytes, j); // @A2C
returnBytes[j] = (byte)(repeater >>> 8); //@P0A
returnBytes[j+1] = (byte)repeater; //@P0A
j += REPEATER_SIZE;
}
i += REPEATER_RECORD_SIZE;
}
// Error (don't have a complete RLE repeater record before EOD)
else
{
if (traceError)
Trace.log(Trace.ERROR, "Don't have a complete RLE repeater record before EOD ...");
throw new InternalErrorException(InternalErrorException.SYNTAX_ERROR );
}
}
}
// End of source reached and have single escape byte
else
{
if (traceError)
Trace.log(Trace.ERROR, "Don't have a complete RLE escape record before EOD ...");
throw new InternalErrorException(InternalErrorException.SYNTAX_ERROR );
}
}
// No RLE record found; just copy bytes from source to destination array
else
{
returnBytes[j++] = source[i++];
}
}
return returnBytes;
}
else if ((destination.length > j) && (reallocate))
{
// Destination array too big. Create a return buffer of the correct length,
// copy data into it, and return the decompressed bytes.
returnBytes = new byte[j];
System.arraycopy(destination, destinationOffset, returnBytes, 0, returnBytes.length);
return returnBytes;
}
else
{
// Destination array is correct length. Return the decompressed bytes.
return destination;
}
}
}