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 }