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 org.jboss.netty.handler.codec.compression;
    17
    18  import org.jboss.netty.buffer.ChannelBuffer;
    19  import org.jboss.netty.buffer.ChannelBuffers;
    20  import org.jboss.netty.channel.Channel;
    21  import org.jboss.netty.channel.ChannelHandlerContext;
    22  import org.jboss.netty.handler.codec.oneone.OneToOneDecoder;
    23  import org.jboss.netty.util.internal.jzlib.JZlib;
    24  import org.jboss.netty.util.internal.jzlib.ZStream;
    25
    26
    27  /**
    28   * Decompresses a {@link ChannelBuffer} using the deflate algorithm.
    29   * @apiviz.landmark
    30   * @apiviz.has org.jboss.netty.handler.codec.compression.ZlibWrapper
    31   */
    32  public class ZlibDecoder extends OneToOneDecoder {
    33
    34      private final ZStream z = new ZStream();
    35      private byte[] dictionary;
    36      private volatile boolean finished;
    37
    38      /**
    39       * Creates a new instance with the default wrapper ({@link ZlibWrapper#ZLIB}).
    40       *
    41       * @throws CompressionException if failed to initialize zlib
    42       */
    43      public ZlibDecoder() {
    44          this(ZlibWrapper.ZLIB);
    45      }
    46
    47      /**
    48       * Creates a new instance with the specified wrapper.
    49       *
    50       * @throws CompressionException if failed to initialize zlib
    51       */
    52      public ZlibDecoder(ZlibWrapper wrapper) {
    53          if (wrapper == null) {
    54              throw new NullPointerException("wrapper");
    55          }
    56
    57          synchronized (z) {
    58              int resultCode = z.inflateInit(ZlibUtil.convertWrapperType(wrapper));
    59              if (resultCode != JZlib.Z_OK) {
    60                  ZlibUtil.fail(z, "initialization failure", resultCode);
    61              }
    62          }
    63      }
    64
    65      /**
    66       * Creates a new instance with the specified preset dictionary. The wrapper
    67       * is always {@link ZlibWrapper#ZLIB} because it is the only format that
    68       * supports the preset dictionary.
    69       *
    70       * @throws CompressionException if failed to initialize zlib
    71       */
    72      public ZlibDecoder(byte[] dictionary) {
    73          if (dictionary == null) {
    74              throw new NullPointerException("dictionary");
    75          }
    76          this.dictionary = dictionary;
    77
    78          synchronized (z) {
    79              int resultCode;
    80              resultCode = z.inflateInit(JZlib.W_ZLIB);
    81              if (resultCode != JZlib.Z_OK) {
    82                  ZlibUtil.fail(z, "initialization failure", resultCode);
    83              }
    84          }
    85      }
    86
    87      /**
    88       * Returns {@code true} if and only if the end of the compressed stream
    89       * has been reached.
    90       */
    91      public boolean isClosed() {
    92          return finished;
    93      }
    94
    95      @Override
    96      protected Object decode(ChannelHandlerContext ctx, Channel channel, Object msg) throws Exception {
    97          if (!(msg instanceof ChannelBuffer) || finished) {
    98              return msg;
    99          }
    100
    101         synchronized (z) {
    102             try {
    103                 // Configure input.
    104                 ChannelBuffer compressed = (ChannelBuffer) msg;
    105                 byte[] in = new byte[compressed.readableBytes()];
    106                 compressed.readBytes(in);
    107                 z.next_in = in;
    108                 z.next_in_index = 0;
    109                 z.avail_in = in.length;
    110
    111                 // Configure output.
    112                 byte[] out = new byte[in.length << 1];
    113                 ChannelBuffer decompressed = ChannelBuffers.dynamicBuffer(
    114                         compressed.order(), out.length,
    115                         ctx.getChannel().getConfig().getBufferFactory());
    116                 z.next_out = out;
    117                 z.next_out_index = 0;
    118                 z.avail_out = out.length;
    119
    120
    121                 loop: for (;;) {
    122                     // Decompress 'in' into 'out'
    123                     int resultCode = z.inflate(JZlib.Z_SYNC_FLUSH);
    124                     if (z.next_out_index > 0) {
    125                         decompressed.writeBytes(out, 0, z.next_out_index);
    126                         z.avail_out = out.length;
    127                     }
    128                     z.next_out_index = 0;
    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
    157                 if (decompressed.writerIndex() != 0) { // readerIndex is always 0
    158                     return decompressed;
    159                 } else {
    160                     return null;
    161                 }
    162             } finally {
    163                 // Deference the external references explicitly to tell the VM that
    164                 // the allocated byte arrays are temporary so that the call stack
    165                 // can be utilized.
    166                 // I'm not sure if the modern VMs do this optimization though.
    167                 z.next_in = null;
    168                 z.next_out = null;
    169             }
    170         }
    171     }
    172 }