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(
84                      "invalid compression level specified");
85          }
86  
87          // create a new instance of ZStream. This will be done only once.
88          zStream = new ZStream();
89  
90          switch (mode) {
91          case MODE_DEFLATER:
92              zStream.deflateInit(this.compressionLevel);
93              break;
94          case MODE_INFLATER:
95              zStream.inflateInit();
96              break;
97          default:
98              throw new IllegalArgumentException("invalid mode specified");
99          }
100         this.mode = mode;
101     }
102 
103     /**
104      * Uncompress the given buffer, returning it in a new buffer.
105      * 
106      * @param inBuffer the {@link IoBuffer} to be decompressed. The contents
107      * of the buffer are transferred into a local byte array and the buffer is
108      * flipped and returned intact.
109      * @return the decompressed data
110      * @throws IOException if the decompression of the data failed for some reason.
111      * @throws IllegalArgumentException if the mode is not <code>MODE_DEFLATER</code>
112      */
113     public IoBuffer inflate(IoBuffer inBuffer) throws IOException {
114         if (mode == MODE_DEFLATER) {
115             throw new IllegalStateException("not initialized as INFLATER");
116         }
117 
118         byte[] inBytes = new byte[inBuffer.remaining()];
119         inBuffer.get(inBytes).flip();
120 
121         // We could probably do this better, if we're willing to return multiple buffers
122         // (e.g. with a callback function)
123         byte[] outBytes = new byte[inBytes.length * 2];
124         IoBuffer outBuffer = IoBuffer.allocate(outBytes.length);
125         outBuffer.setAutoExpand(true);
126 
127         synchronized( zStream ) {
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 
161         return outBuffer.flip();
162     }
163 
164     /**
165      * Compress the input. The result will be put in a new buffer.
166      *  
167      * @param inBuffer the buffer to be compressed. The contents are transferred
168      * into a local byte array and the buffer is flipped and returned intact.
169      * @return the buffer with the compressed data
170      * @throws IOException if the compression of teh buffer failed for some reason
171      * @throws IllegalStateException if the mode is not <code>MODE_DEFLATER</code>
172      */
173     public IoBuffer deflate(IoBuffer inBuffer) throws IOException {
174         if (mode == MODE_INFLATER) {
175             throw new IllegalStateException("not initialized as DEFLATER");
176         }
177 
178         byte[] inBytes = new byte[inBuffer.remaining()];
179         inBuffer.get(inBytes).flip();
180 
181         // according to spec, destination buffer should be 0.1% larger
182         // than source length plus 12 bytes. We add a single byte to safeguard
183         // against rounds that round down to the smaller value
184         int outLen = (int) Math.round(inBytes.length * 1.001) + 1 + 12;
185         byte[] outBytes = new byte[outLen];
186 
187         synchronized(zStream) {
188             zStream.next_in = inBytes;
189             zStream.next_in_index = 0;
190             zStream.avail_in = inBytes.length;
191             zStream.next_out = outBytes;
192             zStream.next_out_index = 0;
193             zStream.avail_out = outBytes.length;
194     
195             int retval = zStream.deflate(JZlib.Z_SYNC_FLUSH);
196             if (retval != JZlib.Z_OK) {
197                 outBytes = null;
198                 inBytes = null;
199                 throw new IOException("Compression failed with return value : "
200                         + retval);
201             }
202     
203             IoBuffer outBuf = IoBuffer
204                     .wrap(outBytes, 0, zStream.next_out_index);
205 
206             return outBuf;
207         }
208     }
209 
210     /**
211      * Cleans up the resources used by the compression library.
212      */
213     public void cleanUp() {
214         if (zStream != null) {
215             zStream.free();
216         }
217     }
218 }