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.support;
    21
    22  import java.io.IOException;
    23
    24  import org.apache.mina.common.ByteBuffer;
    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 The Apache Directory Project (mina-dev@directory.apache.org)
    35   * @version $Rev: 629330 $, $Date: 2008-02-20 12:18:28 +0900 (Wed, 20 Feb 2008) $
    36   */
    37  public class Zlib {
    38      public static final int COMPRESSION_MAX = JZlib.Z_BEST_COMPRESSION;
    39
    40      public static final int COMPRESSION_MIN = JZlib.Z_BEST_SPEED;
    41
    42      public static final int COMPRESSION_NONE = JZlib.Z_NO_COMPRESSION;
    43
    44      public static final int COMPRESSION_DEFAULT = JZlib.Z_DEFAULT_COMPRESSION;
    45
    46      public static final int MODE_DEFLATER = 1;
    47
    48      public static final int MODE_INFLATER = 2;
    49
    50      private int compressionLevel;
    51
    52      private ZStream zStream = null;
    53
    54      private int mode = -1;
    55
    56      /**
    57       * @param compressionLevel the level of compression that should be used
    58       * @param mode the mode in which the instance will operate. Can be either
    59       * of <tt>MODE_DEFLATER</tt> or <tt>MODE_INFLATER</tt>
    60       */
    61      public Zlib(int compressionLevel, int mode) {
    62          switch (compressionLevel) {
    63          case COMPRESSION_MAX:
    64          case COMPRESSION_MIN:
    65          case COMPRESSION_NONE:
    66          case COMPRESSION_DEFAULT:
    67              this.compressionLevel = compressionLevel;
    68              break;
    69          default:
    70              throw new IllegalArgumentException(
    71                      "invalid compression level specified");
    72          }
    73
    74          // create a new instance of ZStream. This will be done only once.
    75          zStream = new ZStream();
    76
    77          switch (mode) {
    78          case MODE_DEFLATER:
    79              zStream.deflateInit(this.compressionLevel);
    80              break;
    81          case MODE_INFLATER:
    82              zStream.inflateInit();
    83              break;
    84          default:
    85              throw new IllegalArgumentException("invalid mode specified");
    86          }
    87          this.mode = mode;
    88      }
    89
    90      /**
    91       * @param inBuffer the {@link ByteBuffer} to be decompressed. The contents
    92       * of the buffer are transferred into a local byte array and the buffer is
    93       * flipped and returned intact.
    94       * @return the decompressed data. If not passed to the MINA methods that
    95       * release the buffer automatically, the buffer has to be manually released
    96       * @throws IOException if the decompression of the data failed for some reason.
    97       */
    98      public ByteBuffer inflate(ByteBuffer inBuffer) throws IOException {
    99          if (mode == MODE_DEFLATER) {
    100             throw new IllegalStateException("not initialized as INFLATER");
    101         }
    102
    103         byte[] inBytes = new byte[inBuffer.remaining()];
    104         inBuffer.get(inBytes).flip();
    105
    106         // We could probably do this better, if we're willing to return multiple buffers
    107         //  (e.g. with a callback function)
    108         byte[] outBytes = new byte[inBytes.length * 2];
    109         ByteBuffer outBuffer = ByteBuffer.allocate(outBytes.length);
    110         outBuffer.setAutoExpand(true);
    111
    112         zStream.next_in = inBytes;
    113         zStream.next_in_index = 0;
    114         zStream.avail_in = inBytes.length;
    115         zStream.next_out = outBytes;
    116         zStream.next_out_index = 0;
    117         zStream.avail_out = outBytes.length;
    118         int retval = 0;
    119
    120         do {
    121             retval = zStream.inflate(JZlib.Z_SYNC_FLUSH);
    122             switch (retval) {
    123             case JZlib.Z_OK:
    124                 // completed decompression, lets copy data and get out
    125             case JZlib.Z_BUF_ERROR:
    126                 // need more space for output. store current output and get more
    127                 outBuffer.put(outBytes, 0, zStream.next_out_index);
    128                 zStream.next_out_index = 0;
    129                 zStream.avail_out = outBytes.length;
    130                 break;
    131             default:
    132                 // unknown error
    133                 outBuffer.release();
    134                 outBuffer = null;
    135                 if (zStream.msg == null)
    136                     throw new IOException("Unknown error. Error code : "
    137                             + retval);
    138                 else
    139                     throw new IOException("Unknown error. Error code : "
    140                             + retval + " and message : " + zStream.msg);
    141             }
    142         } while (zStream.avail_in > 0);
    143
    144         return outBuffer.flip();
    145     }
    146
    147     /**
    148      * @param inBuffer the buffer to be compressed. The contents are transferred
    149      * into a local byte array and the buffer is flipped and returned intact.
    150      * @return the buffer with the compressed data. If not passed to any of the
    151      * MINA methods that automatically release the buffer, the buffer has to be
    152      * released manually.
    153      * @throws IOException if the compression of teh buffer failed for some reason
    154      */
    155     public ByteBuffer deflate(ByteBuffer inBuffer) throws IOException {
    156         if (mode == MODE_INFLATER) {
    157             throw new IllegalStateException("not initialized as DEFLATER");
    158         }
    159
    160         byte[] inBytes = new byte[inBuffer.remaining()];
    161         inBuffer.get(inBytes).flip();
    162
    163         // according to spec, destination buffer should be 0.1% larger
    164         // than source length plus 12 bytes. We add a single byte to safeguard
    165         // against rounds that round down to the smaller value
    166         int outLen = (int) Math.round(inBytes.length * 1.001) + 1 + 12;
    167         byte[] outBytes = new byte[outLen];
    168
    169         zStream.next_in = inBytes;
    170         zStream.next_in_index = 0;
    171         zStream.avail_in = inBytes.length;
    172         zStream.next_out = outBytes;
    173         zStream.next_out_index = 0;
    174         zStream.avail_out = outBytes.length;
    175
    176         int retval = zStream.deflate(JZlib.Z_SYNC_FLUSH);
    177         if (retval != JZlib.Z_OK) {
    178             outBytes = null;
    179             inBytes = null;
    180             throw new IOException("Compression failed with return value : "
    181                     + retval);
    182         }
    183
    184         ByteBuffer outBuf = ByteBuffer
    185                 .wrap(outBytes, 0, zStream.next_out_index);
    186
    187         return outBuf;
    188     }
    189
    190     /**
    191      * Cleans up the resources used by the compression library.
    192      */
    193     public void cleanUp() {
    194         if (zStream != null)
    195             zStream.free();
    196     }
    197 }