BZip2.java

// Copyright (c) ZeroC, Inc.

package com.zeroc.Ice;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

/**
 * @hidden Public because it's used by the 'Ice/operations' test.
 */
public class BZip2 {
    public static Buffer compress(Buffer buf, int headerSize, int compressionLevel) {
        assert (supported());

        int uncompressedLen = buf.size() - headerSize;
        int compressedLen = (int) (uncompressedLen * 1.01 + 600);
        byte[] compressed = new byte[compressedLen];

        byte[] data = null;
        int offset = 0;
        try {
            //
            // If the ByteBuffer is backed by an array then we can avoid
            // an extra copy by using the array directly.
            //
            data = buf.b.array();
            offset = buf.b.arrayOffset();
        } catch (Exception ex) {
            //
            // Otherwise, allocate an array to hold a copy of the uncompressed data.
            //
            data = new byte[buf.size()];
            buf.position(0);
            buf.b.get(data);
        }

        try {
            //
            // Compress the data using the class
            // org.apache.commons.compress.compressors.bzip2.BZip2CompressorOutputStream
            // Its constructor requires an OutputStream argument, therefore we pass the
            // compressed buffer in an OutputStream wrapper.
            //
            BufferedOutputStream bos = new BufferedOutputStream(compressed);
            java.lang.Object[] args =
                new java.lang.Object[]{bos, Integer.valueOf(compressionLevel)};
            OutputStream os = (OutputStream) _bzOutputStreamCtor.newInstance(args);
            os.write(data, offset + headerSize, uncompressedLen);
            os.close();
            compressedLen = bos.pos();
        } catch (Exception ex) {
            throw new ProtocolException("bzip2 compression failed", ex);
        }

        //
        // Don't bother if the compressed data is larger than the uncompressed data.
        //
        if (compressedLen >= uncompressedLen) {
            return null;
        }

        Buffer r = new Buffer(false);
        r.resize(headerSize + 4 + compressedLen, false);
        r.position(0);

        //
        // Copy the header from the uncompressed stream to the compressed one.
        //
        r.b.put(data, offset, headerSize);

        //
        // Add the size of the uncompressed stream before the message body.
        //
        r.b.putInt(buf.size());

        //
        // Add the compressed message body.
        //
        r.b.put(compressed, 0, compressedLen);

        return r;
    }

    public static Buffer uncompress(Buffer buf, int headerSize, int messageSizeMax) {
        assert (supported());

        buf.position(headerSize);
        int uncompressedSize = buf.b.getInt();
        if (uncompressedSize <= headerSize) {
            throw new MarshalException(
                "Unexpected message size after uncompress: " + uncompressedSize);
        }
        if (uncompressedSize > messageSizeMax) {
            Ex.throwMemoryLimitException(uncompressedSize, messageSizeMax);
        }

        int compressedLen = buf.size() - headerSize - 4;

        byte[] compressed = null;
        int offset = 0;
        try {
            //
            // If the ByteBuffer is backed by an array then we can avoid
            // an extra copy by using the array directly.
            //
            compressed = buf.b.array();
            offset = buf.b.arrayOffset();
        } catch (Exception ex) {
            //
            // Otherwise, allocate an array to hold a copy of the compressed data.
            //
            compressed = new byte[buf.size()];
            buf.position(0);
            buf.b.get(compressed);
        }

        Buffer r = new Buffer(false);
        r.resize(uncompressedSize, false);

        try {
            //
            // Uncompress the data using the class
            // org.apache.commons.compress.compressors.bzip2.BZip2CompressorInputStream.
            // Its constructor requires an InputStream argument, therefore we pass the
            // compressed data in a ByteArrayInputStream.
            //
            ByteArrayInputStream bais =
                new ByteArrayInputStream(
                    compressed, offset + headerSize + 4, compressedLen);

            java.lang.Object[] args = new java.lang.Object[]{bais};
            InputStream is = (InputStream) _bzInputStreamCtor.newInstance(args);
            r.position(headerSize);
            byte[] arr = new byte[8 * 1024];
            int n;
            while ((n = is.read(arr)) != -1) {
                r.b.put(arr, 0, n);
            }
            is.close();
        } catch (Exception ex) {
            throw new ProtocolException("bzip2 uncompression failed", ex);
        }

        //
        // Copy the header from the compressed stream to the uncompressed one.
        //
        r.position(0);
        r.b.put(compressed, offset, headerSize);

        return r;
    }

    private static boolean _checked;
    private static java.lang.reflect.Constructor<?> _bzInputStreamCtor;
    private static java.lang.reflect.Constructor<?> _bzOutputStreamCtor;

    public static synchronized boolean supported() {
        //
        // Use lazy initialization when determining whether support for bzip2 compression is
        // available.
        //
        if (!_checked) {
            _checked = true;
            try {
                Class<?> cls;
                Class<?>[] types = new Class<?>[1];
                cls =
                    Util.findClass(
                        "org.apache.commons.compress.compressors.bzip2.BZip2CompressorInputStream",
                        null);
                if (cls != null) {
                    types[0] = InputStream.class;
                    _bzInputStreamCtor = cls.getDeclaredConstructor(types);
                }
                cls =
                    Util.findClass(
                        "org.apache.commons.compress.compressors.bzip2.BZip2CompressorOutputStream",
                        null);
                if (cls != null) {
                    types = new Class<?>[2];
                    types[0] = OutputStream.class;
                    types[1] = Integer.TYPE;
                    _bzOutputStreamCtor = cls.getDeclaredConstructor(types);
                }
            } catch (Exception ex) {
                // Ignore - bzip2 compression not available.
            }
        }
        return _bzInputStreamCtor != null && _bzOutputStreamCtor != null;
    }

    private static class BufferedOutputStream extends OutputStream {
        BufferedOutputStream(byte[] data) {
            _data = data;
        }

        @Override
        public void close() throws IOException {}

        @Override
        public void flush() throws IOException {}

        @Override
        public void write(byte[] b) throws IOException {
            assert (_data.length - _pos >= b.length);
            System.arraycopy(b, 0, _data, _pos, b.length);
            _pos += b.length;
        }

        @Override
        public void write(byte[] b, int off, int len) throws IOException {
            assert (_data.length - _pos >= len);
            System.arraycopy(b, off, _data, _pos, len);
            _pos += len;
        }

        @Override
        public void write(int b) throws IOException {
            assert (_data.length - _pos >= 1);
            _data[_pos] = (byte) b;
            ++_pos;
        }

        int pos() {
            return _pos;
        }

        private byte[] _data;
        private int _pos;
    }

    private BZip2() {}
}