View Javadoc
1   /*
2    * Copyright 2012 The Netty Project
3    *
4    * The Netty Project licenses this file to you under the Apache License,
5    * version 2.0 (the "License"); you may not use this file except in compliance
6    * with the License. You may obtain a copy of the License at:
7    *
8    *   http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations
14   * under the License.
15   */
16  package io.netty.handler.codec.compression;
17  
18  import com.jcraft.jzlib.Inflater;
19  import com.jcraft.jzlib.JZlib;
20  import io.netty.buffer.ByteBuf;
21  import io.netty.channel.ChannelHandlerContext;
22  
23  import java.util.List;
24  
25  public class JZlibDecoder extends ZlibDecoder {
26  
27      private final Inflater z = new Inflater();
28      private byte[] dictionary;
29      private volatile boolean finished;
30  
31      /**
32       * Creates a new instance with the default wrapper ({@link ZlibWrapper#ZLIB}).
33       *
34       * @throws DecompressionException if failed to initialize zlib
35       */
36      public JZlibDecoder() {
37          this(ZlibWrapper.ZLIB);
38      }
39  
40      /**
41       * Creates a new instance with the specified wrapper.
42       *
43       * @throws DecompressionException if failed to initialize zlib
44       */
45      public JZlibDecoder(ZlibWrapper wrapper) {
46          if (wrapper == null) {
47              throw new NullPointerException("wrapper");
48          }
49  
50          int resultCode = z.init(ZlibUtil.convertWrapperType(wrapper));
51          if (resultCode != JZlib.Z_OK) {
52              ZlibUtil.fail(z, "initialization failure", resultCode);
53          }
54      }
55  
56      /**
57       * Creates a new instance with the specified preset dictionary. The wrapper
58       * is always {@link ZlibWrapper#ZLIB} because it is the only format that
59       * supports the preset dictionary.
60       *
61       * @throws DecompressionException if failed to initialize zlib
62       */
63      public JZlibDecoder(byte[] dictionary) {
64          if (dictionary == null) {
65              throw new NullPointerException("dictionary");
66          }
67          this.dictionary = dictionary;
68  
69          int resultCode;
70          resultCode = z.inflateInit(JZlib.W_ZLIB);
71          if (resultCode != JZlib.Z_OK) {
72              ZlibUtil.fail(z, "initialization failure", resultCode);
73          }
74      }
75  
76      /**
77       * Returns {@code true} if and only if the end of the compressed stream
78       * has been reached.
79       */
80      @Override
81      public boolean isClosed() {
82          return finished;
83      }
84  
85      @Override
86      protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
87          if (finished) {
88              // Skip data received after finished.
89              in.skipBytes(in.readableBytes());
90              return;
91          }
92  
93          final int inputLength = in.readableBytes();
94          if (inputLength == 0) {
95              return;
96          }
97  
98          try {
99              // Configure input.
100             z.avail_in = inputLength;
101             if (in.hasArray()) {
102                 z.next_in = in.array();
103                 z.next_in_index = in.arrayOffset() + in.readerIndex();
104             } else {
105                 byte[] array = new byte[inputLength];
106                 in.getBytes(in.readerIndex(), array);
107                 z.next_in = array;
108                 z.next_in_index = 0;
109             }
110             final int oldNextInIndex = z.next_in_index;
111 
112             // Configure output.
113             ByteBuf decompressed = ctx.alloc().heapBuffer(inputLength << 1);
114 
115             try {
116                 loop: for (;;) {
117                     decompressed.ensureWritable(z.avail_in << 1);
118                     z.avail_out = decompressed.writableBytes();
119                     z.next_out = decompressed.array();
120                     z.next_out_index = decompressed.arrayOffset() + decompressed.writerIndex();
121                     int oldNextOutIndex = z.next_out_index;
122 
123                     // Decompress 'in' into 'out'
124                     int resultCode = z.inflate(JZlib.Z_SYNC_FLUSH);
125                     int outputLength = z.next_out_index - oldNextOutIndex;
126                     if (outputLength > 0) {
127                         decompressed.writerIndex(decompressed.writerIndex() + outputLength);
128                     }
129 
130                     switch (resultCode) {
131                     case JZlib.Z_NEED_DICT:
132                         if (dictionary == null) {
133                             ZlibUtil.fail(z, "decompression failure", resultCode);
134                         } else {
135                             resultCode = z.inflateSetDictionary(dictionary, dictionary.length);
136                             if (resultCode != JZlib.Z_OK) {
137                                 ZlibUtil.fail(z, "failed to set the dictionary", resultCode);
138                             }
139                         }
140                         break;
141                     case JZlib.Z_STREAM_END:
142                         finished = true; // Do not decode anymore.
143                         z.inflateEnd();
144                         break loop;
145                     case JZlib.Z_OK:
146                         break;
147                     case JZlib.Z_BUF_ERROR:
148                         if (z.avail_in <= 0) {
149                             break loop;
150                         }
151                         break;
152                     default:
153                         ZlibUtil.fail(z, "decompression failure", resultCode);
154                     }
155                 }
156             } finally {
157                 in.skipBytes(z.next_in_index - oldNextInIndex);
158                 if (decompressed.isReadable()) {
159                     out.add(decompressed);
160                 } else {
161                     decompressed.release();
162                 }
163             }
164         } finally {
165             // Deference the external references explicitly to tell the VM that
166             // the allocated byte arrays are temporary so that the call stack
167             // can be utilized.
168             // I'm not sure if the modern VMs do this optimization though.
169             z.next_in = null;
170             z.next_out = null;
171         }
172     }
173 }