View Javadoc

    1   /*
    2    *  Licensed to the Apache Software Foundation (ASF) under one
    3    *  or more contributor license agreements.  See the NOTICE file
    4    *  distributed with this work for additional information
    5    *  regarding copyright ownership.  The ASF licenses this file
    6    *  to you under the Apache License, Version 2.0 (the
    7    *  "License"); you may not use this file except in compliance
    8    *  with the License.  You may obtain a copy of the License at
    9    *
    10   *    http://www.apache.org/licenses/LICENSE-2.0
    11   *
    12   *  Unless required by applicable law or agreed to in writing,
    13   *  software distributed under the License is distributed on an
    14   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    15   *  KIND, either express or implied.  See the License for the
    16   *  specific language governing permissions and limitations
    17   *  under the License.
    18   *
    19   */
    20  package org.apache.mina.filter.compression;
    21
    22  import java.io.IOException;
    23
    24  import org.apache.mina.core.buffer.IoBuffer;
    25
    26  import com.jcraft.jzlib.JZlib;
    27  import com.jcraft.jzlib.ZStream;
    28
    29  /**
    30   * A helper class for interfacing with the JZlib library. This class acts both
    31   * as a compressor and decompressor, but only as one at a time.  The only
    32   * flush method supported is <tt>Z_SYNC_FLUSH</tt> also known as <tt>Z_PARTIAL_FLUSH</tt>
    33   *
    34   * @author <a href="http://mina.apache.org">Apache MINA Project</a>
    35   */
    36  class Zlib {
    37      /** Try o get the best possible compression */
    38      public static final int COMPRESSION_MAX = JZlib.Z_BEST_COMPRESSION;
    39
    40      /** Favor speed over compression ratio */
    41      public static final int COMPRESSION_MIN = JZlib.Z_BEST_SPEED;
    42
    43      /** No compression */
    44      public static final int COMPRESSION_NONE = JZlib.Z_NO_COMPRESSION;
    45
    46      /** Default compression */
    47      public static final int COMPRESSION_DEFAULT = JZlib.Z_DEFAULT_COMPRESSION;
    48
    49      /** Compression mode */
    50      public static final int MODE_DEFLATER = 1;
    51
    52      /** Uncompress mode */
    53      public static final int MODE_INFLATER = 2;
    54
    55      /** The requested compression level */
    56      private int compressionLevel;
    57
    58      /** The inner stream used to inflate or deflate the data */
    59      private ZStream zStream = null;
    60
    61      /** The selected operation mode : INFLATE or DEFLATE */
    62      private int mode = -1;
    63
    64      /**
    65       * Creates an instance of the ZLib class.
    66       *
    67       * @param compressionLevel the level of compression that should be used. One of
    68       * <tt>COMPRESSION_MAX</tt>, <tt>COMPRESSION_MIN</tt>,
    69       * <tt>COMPRESSION_NONE</tt> or <tt>COMPRESSION_DEFAULT</tt>
    70       * @param mode the mode in which the instance will operate. Can be either
    71       * of <tt>MODE_DEFLATER</tt> or <tt>MODE_INFLATER</tt>
    72       * @throws IllegalArgumentException if the mode is incorrect
    73       */
    74      public Zlib(int compressionLevel, int mode) {
    75          switch (compressionLevel) {
    76          case COMPRESSION_MAX:
    77          case COMPRESSION_MIN:
    78          case COMPRESSION_NONE:
    79          case COMPRESSION_DEFAULT:
    80              this.compressionLevel = compressionLevel;
    81              break;
    82          default:
    83              throw new IllegalArgumentException("invalid compression level specified");
    84          }
    85
    86          // create a new instance of ZStream. This will be done only once.
    87          zStream = new ZStream();
    88
    89          switch (mode) {
    90          case MODE_DEFLATER:
    91              zStream.deflateInit(this.compressionLevel);
    92              break;
    93          case MODE_INFLATER:
    94              zStream.inflateInit();
    95              break;
    96          default:
    97              throw new IllegalArgumentException("invalid mode specified");
    98          }
    99          this.mode = mode;
    100     }
    101
    102     /**
    103      * Uncompress the given buffer, returning it in a new buffer.
    104      *
    105      * @param inBuffer the {@link IoBuffer} to be decompressed. The contents
    106      * of the buffer are transferred into a local byte array and the buffer is
    107      * flipped and returned intact.
    108      * @return the decompressed data
    109      * @throws IOException if the decompression of the data failed for some reason.
    110      * @throws IllegalArgumentException if the mode is not <code>MODE_DEFLATER</code>
    111      */
    112     public IoBuffer inflate(IoBuffer inBuffer) throws IOException {
    113         if (mode == MODE_DEFLATER) {
    114             throw new IllegalStateException("not initialized as INFLATER");
    115         }
    116
    117         byte[] inBytes = new byte[inBuffer.remaining()];
    118         inBuffer.get(inBytes).flip();
    119
    120         // We could probably do this better, if we're willing to return multiple buffers
    121         // (e.g. with a callback function)
    122         byte[] outBytes = new byte[inBytes.length * 2];
    123         IoBuffer outBuffer = IoBuffer.allocate(outBytes.length);
    124         outBuffer.setAutoExpand(true);
    125
    126         synchronized (zStream) {
    127             zStream.next_in = inBytes;
    128             zStream.next_in_index = 0;
    129             zStream.avail_in = inBytes.length;
    130             zStream.next_out = outBytes;
    131             zStream.next_out_index = 0;
    132             zStream.avail_out = outBytes.length;
    133             int retval = 0;
    134
    135             do {
    136                 retval = zStream.inflate(JZlib.Z_SYNC_FLUSH);
    137                 switch (retval) {
    138                 case JZlib.Z_OK:
    139                     // completed decompression, lets copy data and get out
    140                 case JZlib.Z_BUF_ERROR:
    141                     // need more space for output. store current output and get more
    142                     outBuffer.put(outBytes, 0, zStream.next_out_index);
    143                     zStream.next_out_index = 0;
    144                     zStream.avail_out = outBytes.length;
    145                     break;
    146                 default:
    147                     // unknown error
    148                     outBuffer = null;
    149                     if (zStream.msg == null) {
    150                         throw new IOException("Unknown error. Error code : " + retval);
    151                     } else {
    152                         throw new IOException("Unknown error. Error code : " + retval + " and message : " + zStream.msg);
    153                     }
    154                 }
    155             } while (zStream.avail_in > 0);
    156         }
    157
    158         return outBuffer.flip();
    159     }
    160
    161     /**
    162      * Compress the input. The result will be put in a new buffer.
    163      *
    164      * @param inBuffer the buffer to be compressed. The contents are transferred
    165      * into a local byte array and the buffer is flipped and returned intact.
    166      * @return the buffer with the compressed data
    167      * @throws IOException if the compression of teh buffer failed for some reason
    168      * @throws IllegalStateException if the mode is not <code>MODE_DEFLATER</code>
    169      */
    170     public IoBuffer deflate(IoBuffer inBuffer) throws IOException {
    171         if (mode == MODE_INFLATER) {
    172             throw new IllegalStateException("not initialized as DEFLATER");
    173         }
    174
    175         byte[] inBytes = new byte[inBuffer.remaining()];
    176         inBuffer.get(inBytes).flip();
    177
    178         // according to spec, destination buffer should be 0.1% larger
    179         // than source length plus 12 bytes. We add a single byte to safeguard
    180         // against rounds that round down to the smaller value
    181         int outLen = (int) Math.round(inBytes.length * 1.001) + 1 + 12;
    182         byte[] outBytes = new byte[outLen];
    183
    184         synchronized (zStream) {
    185             zStream.next_in = inBytes;
    186             zStream.next_in_index = 0;
    187             zStream.avail_in = inBytes.length;
    188             zStream.next_out = outBytes;
    189             zStream.next_out_index = 0;
    190             zStream.avail_out = outBytes.length;
    191
    192             int retval = zStream.deflate(JZlib.Z_SYNC_FLUSH);
    193             if (retval != JZlib.Z_OK) {
    194                 outBytes = null;
    195                 inBytes = null;
    196                 throw new IOException("Compression failed with return value : " + retval);
    197             }
    198
    199             IoBuffer outBuf = IoBuffer.wrap(outBytes, 0, zStream.next_out_index);
    200
    201             return outBuf;
    202         }
    203     }
    204
    205     /**
    206      * Cleans up the resources used by the compression library.
    207      */
    208     public void cleanUp() {
    209         if (zStream != null) {
    210             zStream.free();
    211         }
    212     }
    213 }