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