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 }