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             int oldNextInIndex = z.next_in_index;
111 
112             // Configure output.
113             int maxOutputLength = inputLength << 1;
114             ByteBuf decompressed = ctx.alloc().heapBuffer(maxOutputLength);
115 
116             try {
117                 loop: for (;;) {
118                     z.avail_out = maxOutputLength;
119                     decompressed.ensureWritable(maxOutputLength);
120                     z.next_out = decompressed.array();
121                     z.next_out_index = decompressed.arrayOffset() + decompressed.writerIndex();
122                     int oldNextOutIndex = z.next_out_index;
123 
124                     // Decompress 'in' into 'out'
125                     int resultCode = z.inflate(JZlib.Z_SYNC_FLUSH);
126                     int outputLength = z.next_out_index - oldNextOutIndex;
127                     if (outputLength > 0) {
128                         decompressed.writerIndex(decompressed.writerIndex() + outputLength);
129                     }
130 
131                     switch (resultCode) {
132                     case JZlib.Z_NEED_DICT:
133                         if (dictionary == null) {
134                             ZlibUtil.fail(z, "decompression failure", resultCode);
135                         } else {
136                             resultCode = z.inflateSetDictionary(dictionary, dictionary.length);
137                             if (resultCode != JZlib.Z_OK) {
138                                 ZlibUtil.fail(z, "failed to set the dictionary", resultCode);
139                             }
140                         }
141                         break;
142                     case JZlib.Z_STREAM_END:
143                         finished = true; // Do not decode anymore.
144                         z.inflateEnd();
145                         break loop;
146                     case JZlib.Z_OK:
147                         break;
148                     case JZlib.Z_BUF_ERROR:
149                         if (z.avail_in <= 0) {
150                             break loop;
151                         }
152                         break;
153                     default:
154                         ZlibUtil.fail(z, "decompression failure", resultCode);
155                     }
156                 }
157             } finally {
158                 in.skipBytes(z.next_in_index - oldNextInIndex);
159                 if (decompressed.isReadable()) {
160                     out.add(decompressed);
161                 } else {
162                     decompressed.release();
163                 }
164             }
165         } finally {
166             // Deference the external references explicitly to tell the VM that
167             // the allocated byte arrays are temporary so that the call stack
168             // can be utilized.
169             // I'm not sure if the modern VMs do this optimization though.
170             z.next_in = null;
171             z.next_out = null;
172         }
173     }
174 }