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