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 }