blob: 3908b9183268c46d9cf9e1d2468049f9d8787fad [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.mina.filter.compression;
import java.io.IOException;
import org.apache.mina.core.buffer.IoBuffer;
import com.jcraft.jzlib.JZlib;
import com.jcraft.jzlib.ZStream;
/**
* A helper class for interfacing with the JZlib library. This class acts both
* as a compressor and decompressor, but only as one at a time. The only
* flush method supported is <tt>Z_SYNC_FLUSH</tt> also known as <tt>Z_PARTIAL_FLUSH</tt>
*
* @author <a href="http://mina.apache.org">Apache MINA Project</a>
*/
class Zlib {
/** Try o get the best possible compression */
public static final int COMPRESSION_MAX = JZlib.Z_BEST_COMPRESSION;
/** Favor speed over compression ratio */
public static final int COMPRESSION_MIN = JZlib.Z_BEST_SPEED;
/** No compression */
public static final int COMPRESSION_NONE = JZlib.Z_NO_COMPRESSION;
/** Default compression */
public static final int COMPRESSION_DEFAULT = JZlib.Z_DEFAULT_COMPRESSION;
/** Compression mode */
public static final int MODE_DEFLATER = 1;
/** Uncompress mode */
public static final int MODE_INFLATER = 2;
/** The requested compression level */
private int compressionLevel;
/** The inner stream used to inflate or deflate the data */
private ZStream zStream = null;
/** The selected operation mode : INFLATE or DEFLATE */
private int mode = -1;
/**
* Creates an instance of the ZLib class.
*
* @param compressionLevel the level of compression that should be used. One of
* <tt>COMPRESSION_MAX</tt>, <tt>COMPRESSION_MIN</tt>,
* <tt>COMPRESSION_NONE</tt> or <tt>COMPRESSION_DEFAULT</tt>
* @param mode the mode in which the instance will operate. Can be either
* of <tt>MODE_DEFLATER</tt> or <tt>MODE_INFLATER</tt>
* @throws IllegalArgumentException if the mode is incorrect
*/
public Zlib(int compressionLevel, int mode) {
switch (compressionLevel) {
case COMPRESSION_MAX:
case COMPRESSION_MIN:
case COMPRESSION_NONE:
case COMPRESSION_DEFAULT:
this.compressionLevel = compressionLevel;
break;
default:
throw new IllegalArgumentException("invalid compression level specified");
}
// create a new instance of ZStream. This will be done only once.
zStream = new ZStream();
switch (mode) {
case MODE_DEFLATER:
zStream.deflateInit(this.compressionLevel);
break;
case MODE_INFLATER:
zStream.inflateInit();
break;
default:
throw new IllegalArgumentException("invalid mode specified");
}
this.mode = mode;
}
/**
* Uncompress the given buffer, returning it in a new buffer.
*
* @param inBuffer the {@link IoBuffer} to be decompressed. The contents
* of the buffer are transferred into a local byte array and the buffer is
* flipped and returned intact.
* @return the decompressed data
* @throws IOException if the decompression of the data failed for some reason.
* @throws IllegalArgumentException if the mode is not <code>MODE_DEFLATER</code>
*/
public IoBuffer inflate(IoBuffer inBuffer) throws IOException {
if (mode == MODE_DEFLATER) {
throw new IllegalStateException("not initialized as INFLATER");
}
byte[] inBytes = new byte[inBuffer.remaining()];
inBuffer.get(inBytes).flip();
// We could probably do this better, if we're willing to return multiple buffers
// (e.g. with a callback function)
byte[] outBytes = new byte[inBytes.length * 2];
IoBuffer outBuffer = IoBuffer.allocate(outBytes.length);
outBuffer.setAutoExpand(true);
synchronized (zStream) {
zStream.next_in = inBytes;
zStream.next_in_index = 0;
zStream.avail_in = inBytes.length;
zStream.next_out = outBytes;
zStream.next_out_index = 0;
zStream.avail_out = outBytes.length;
int retval = 0;
do {
retval = zStream.inflate(JZlib.Z_SYNC_FLUSH);
switch (retval) {
case JZlib.Z_OK:
// completed decompression, lets copy data and get out
case JZlib.Z_BUF_ERROR:
// need more space for output. store current output and get more
outBuffer.put(outBytes, 0, zStream.next_out_index);
zStream.next_out_index = 0;
zStream.avail_out = outBytes.length;
break;
default:
// unknown error
outBuffer = null;
if (zStream.msg == null) {
throw new IOException("Unknown error. Error code : " + retval);
} else {
throw new IOException("Unknown error. Error code : " + retval + " and message : " + zStream.msg);
}
}
} while (zStream.avail_in > 0);
cleanUp();
}
return outBuffer.flip();
}
/**
* Compress the input. The result will be put in a new buffer.
*
* @param inBuffer the buffer to be compressed. The contents are transferred
* into a local byte array and the buffer is flipped and returned intact.
* @return the buffer with the compressed data
* @throws IOException if the compression of teh buffer failed for some reason
* @throws IllegalStateException if the mode is not <code>MODE_DEFLATER</code>
*/
public IoBuffer deflate(IoBuffer inBuffer) throws IOException {
if (mode == MODE_INFLATER) {
throw new IllegalStateException("not initialized as DEFLATER");
}
byte[] inBytes = new byte[inBuffer.remaining()];
inBuffer.get(inBytes);
// according to spec, destination buffer should be 0.1% larger
// than source length plus 12 bytes. We add a single byte to safeguard
// against rounds that round down to the smaller value
int outLen = (int) Math.round(inBytes.length * 1.001) + 1 + 12;
byte[] outBytes = new byte[outLen];
synchronized (zStream) {
zStream.next_in = inBytes;
zStream.next_in_index = 0;
zStream.avail_in = inBytes.length;
zStream.next_out = outBytes;
zStream.next_out_index = 0;
zStream.avail_out = outBytes.length;
int retval = zStream.deflate(JZlib.Z_SYNC_FLUSH);
if (retval != JZlib.Z_OK) {
outBytes = null;
inBytes = null;
throw new IOException("Compression failed with return value : " + retval);
}
IoBuffer outBuf = IoBuffer.wrap(outBytes, 0, zStream.next_out_index);
cleanUp();
return outBuf;
}
}
/**
* Cleans up the resources used by the compression library.
*/
public void cleanUp() {
if (zStream != null) {
zStream.free();
}
}
}