View Javadoc
1   /*
2    * Copyright 2021 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.netty5.handler.codec.compression;
17  
18  import com.aayushatharva.brotli4j.encoder.Encoder;
19  import io.netty5.buffer.api.Buffer;
20  import io.netty5.buffer.api.BufferAllocator;
21  
22  import java.io.IOException;
23  import java.util.Objects;
24  import java.util.function.Supplier;
25  
26  import static java.util.Objects.requireNonNull;
27  
28  /**
29   * Compress a {@link Buffer} with the brotli format.
30   *
31   * See <a href="https://github.com/google/brotli">brotli</a>.
32   */
33  public final class BrotliCompressor implements Compressor {
34      private final Encoder.Parameters parameters;
35  
36      private enum State {
37          PROCESSING,
38          FINISHED,
39          CLOSED
40      }
41  
42      private State state = State.PROCESSING;
43  
44      /**
45       * Create a new {@link BrotliCompressor} Instance
46       *
47       * @param parameters {@link Encoder.Parameters} Instance
48       */
49      private BrotliCompressor(Encoder.Parameters parameters) {
50          this.parameters = requireNonNull(parameters, "Parameters");
51      }
52  
53      /**
54       * Create a new {@link BrotliCompressor} factory
55       *
56       * @param parameters {@link Encoder.Parameters} Instance
57       * @return the factory.
58       */
59      public static Supplier<BrotliCompressor> newFactory(Encoder.Parameters parameters) {
60          Objects.requireNonNull(parameters, "parameters");
61          return () -> new BrotliCompressor(parameters);
62      }
63  
64      /**
65       * Create a new {@link BrotliCompressor} factory
66       *
67       * @param brotliOptions {@link BrotliOptions} to use.
68       * @return the factory.
69       */
70      public static Supplier<BrotliCompressor> newFactory(BrotliOptions brotliOptions) {
71          return newFactory(brotliOptions.parameters());
72      }
73  
74      /**
75       * Create a new {@link BrotliCompressor} factory
76       *
77       * with {@link Encoder.Parameters#setQuality(int)} set to 4
78       * and {@link Encoder.Parameters#setMode(Encoder.Mode)} set to {@link Encoder.Mode#TEXT}
79       *
80       * @return the factory.
81       */
82      public static Supplier<BrotliCompressor> newFactory() {
83          return newFactory(BrotliOptions.DEFAULT);
84      }
85  
86      @Override
87      public Buffer compress(Buffer input, BufferAllocator allocator) throws CompressionException {
88          switch (state) {
89              case CLOSED:
90                  throw new CompressionException("Compressor closed");
91              case FINISHED:
92                  return allocator.allocate(0);
93              case PROCESSING:
94                  if (input.readableBytes() == 0) {
95                      return allocator.allocate(0);
96                  }
97                  // TODO: Improve me
98                  byte[] uncompressed = new byte[input.readableBytes()];
99                  try {
100                     input.readBytes(uncompressed, 0, uncompressed.length);
101                     byte[] compressed = Encoder.compress(uncompressed, parameters);
102                     return allocator.copyOf(compressed);
103                 } catch (IOException e) {
104                     state = State.FINISHED;
105                     throw new CompressionException(e);
106                 }
107             default:
108                 throw new IllegalStateException();
109         }
110     }
111 
112     @Override
113     public Buffer finish(BufferAllocator allocator) {
114         switch (state) {
115             case CLOSED:
116                 throw new CompressionException("Compressor closed");
117             case FINISHED:
118             case PROCESSING:
119                 state = State.FINISHED;
120                 return allocator.allocate(0);
121             default:
122                 throw new IllegalStateException();
123         }
124     }
125 
126     @Override
127     public boolean isFinished() {
128         return state != State.PROCESSING;
129     }
130 
131     @Override
132     public boolean isClosed() {
133         return state == State.CLOSED;
134     }
135 
136     @Override
137     public void close() {
138         state = State.CLOSED;
139     }
140 }