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: 555855 $, $Date: 2007-07-13 05:19:00 +0200 (Fri, 13 Jul 2007) $
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.limit()];
104         inBuffer.get(inBytes).flip();
105 
106         byte[] outBytes = new byte[inBytes.length * 2];
107         ByteBuffer outBuffer = ByteBuffer.allocate(outBytes.length);
108         outBuffer.setAutoExpand(true);
109 
110         zStream.next_in = inBytes;
111         zStream.next_in_index = 0;
112         zStream.avail_in = inBytes.length;
113         zStream.next_out = outBytes;
114         zStream.next_out_index = 0;
115         zStream.avail_out = outBytes.length;
116         int retval = 0;
117 
118         do {
119             retval = zStream.inflate(JZlib.Z_SYNC_FLUSH);
120             switch (retval) {
121             case JZlib.Z_OK:
122                 // completed decompression, lets copy data and get out
123             case JZlib.Z_BUF_ERROR:
124                 // need more space for output. store current output and get more
125                 outBuffer.put(outBytes, 0, zStream.next_out_index);
126                 zStream.next_out_index = 0;
127                 zStream.avail_out = outBytes.length;
128                 break;
129             default:
130                 // unknown error
131                 outBuffer.release();
132                 outBuffer = null;
133                 if (zStream.msg == null)
134                     throw new IOException("Unknown error. Error code : "
135                             + retval);
136                 else
137                     throw new IOException("Unknown error. Error code : "
138                             + retval + " and message : " + zStream.msg);
139             }
140         } while (zStream.avail_in > 0);
141 
142         return outBuffer.flip();
143     }
144 
145     /**
146      * @param inBuffer the buffer to be compressed. The contents are transferred
147      * into a local byte array and the buffer is flipped and returned intact.
148      * @return the buffer with the compressed data. If not passed to any of the
149      * MINA methods that automatically release the buffer, the buffer has to be
150      * released manually.
151      * @throws IOException if the compression of teh buffer failed for some reason
152      */
153     public ByteBuffer deflate(ByteBuffer inBuffer) throws IOException {
154         if (mode == MODE_INFLATER) {
155             throw new IllegalStateException("not initialized as DEFLATER");
156         }
157 
158         byte[] inBytes = new byte[inBuffer.limit()];
159         inBuffer.get(inBytes).flip();
160 
161         // according to spec, destination buffer should be 0.1% larger
162         // than source length plus 12 bytes. We add a single byte to safeguard
163         // against rounds that round down to the smaller value
164         int outLen = (int) Math.round(inBytes.length * 1.001) + 1 + 12;
165         byte[] outBytes = new byte[outLen];
166 
167         zStream.next_in = inBytes;
168         zStream.next_in_index = 0;
169         zStream.avail_in = inBytes.length;
170         zStream.next_out = outBytes;
171         zStream.next_out_index = 0;
172         zStream.avail_out = outBytes.length;
173 
174         int retval = zStream.deflate(JZlib.Z_SYNC_FLUSH);
175         if (retval != JZlib.Z_OK) {
176             outBytes = null;
177             inBytes = null;
178             throw new IOException("Compression failed with return value : "
179                     + retval);
180         }
181 
182         ByteBuffer outBuf = ByteBuffer
183                 .wrap(outBytes, 0, zStream.next_out_index);
184 
185         return outBuf;
186     }
187 
188     /**
189      * Cleans up the resources used by the compression library.
190      */
191     public void cleanUp() {
192         if (zStream != null)
193             zStream.free();
194     }
195 }