View Javadoc
1   /*
2    * Copyright 2025 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    *   https://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 io.netty.buffer.ByteBuf;
19  import io.netty.buffer.ByteBufAllocator;
20  import io.netty.buffer.Unpooled;
21  import io.netty.handler.codec.ByteToMessageDecoder;
22  
23  /**
24   * Special decompressor implementation that buffers input so that it can be processed piecemeal, similar to
25   * {@link io.netty.handler.codec.ByteToMessageDecoder}.
26   */
27  abstract class InputBufferingDecompressor implements Decompressor {
28      protected final ByteBufAllocator allocator;
29      private ByteBuf cumulation;
30  
31      InputBufferingDecompressor(ByteBufAllocator allocator) {
32          this.allocator = allocator;
33      }
34  
35      @Override
36      public final void addInput(ByteBuf buf) throws DecompressionException {
37          if (!buf.isReadable()) {
38              buf.release();
39              return;
40          }
41          if (this.cumulation != null) {
42              buf = ByteToMessageDecoder.MERGE_CUMULATOR.cumulate(allocator, this.cumulation, buf);
43              this.cumulation = null;
44          }
45          try {
46              processInput(buf);
47          } catch (Throwable t) {
48              buf.release();
49              throw t;
50          }
51          if (buf.isReadable()) {
52              this.cumulation = buf;
53          } else {
54              buf.release();
55          }
56      }
57  
58      @Override
59      public final ByteBuf takeOutput() throws DecompressionException {
60          ByteBuf buf = cumulation == null ? Unpooled.EMPTY_BUFFER : cumulation;
61          ByteBuf output = processOutput(buf);
62          try {
63              if (status() == Status.NEED_INPUT && buf.isReadable()) {
64                  processInput(buf);
65              }
66          } catch (Throwable t) {
67              output.release();
68              throw t;
69          }
70          if (this.cumulation != null && !this.cumulation.isReadable()) {
71              this.cumulation.release();
72              this.cumulation = null;
73          }
74          return output;
75      }
76  
77      /**
78       * Process some input. The input buffer ownership <i>does not</i> transfer to this method: If there's still data
79       * unread after this method finishes, it will be buffered.
80       *
81       * @param buf The input buffer
82       */
83      abstract void processInput(ByteBuf buf) throws DecompressionException;
84  
85      /**
86       * Produce some output. The input buffer parameter may be read from to consume some more data, but note that this
87       * method <i>must</i> return a buffer even if the input is too short.
88       *
89       * @param buf The input buffer
90       */
91      abstract ByteBuf processOutput(ByteBuf buf) throws DecompressionException;
92  
93      /**
94       * Number of buffered bytes.
95       *
96       * @return Number of buffered bytes
97       */
98      final int available() {
99          return cumulation == null ? 0 : cumulation.readableBytes();
100     }
101 
102     @Override
103     public void close() {
104         if (this.cumulation != null) {
105             this.cumulation.release();
106             this.cumulation = null;
107         }
108     }
109 }