View Javadoc
1   /*
2    * Copyright 2022 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;
17  
18  import io.netty5.buffer.api.Buffer;
19  import io.netty5.channel.ChannelHandler;
20  import io.netty5.channel.ChannelHandlerAdapter;
21  import io.netty5.channel.ChannelHandlerContext;
22  import io.netty5.channel.ChannelPipeline;
23  import io.netty5.util.concurrent.Future;
24  import io.netty5.util.internal.TypeParameterMatcher;
25  
26  import static io.netty5.util.internal.SilentDispose.autoClosing;
27  import static java.util.Objects.requireNonNull;
28  
29  /**
30   * {@link ChannelHandler} which encodes message in a stream-like fashion from one message to a {@link Buffer}.
31   *
32   * <p>Example implementation which encodes {@link Integer}s to a {@link Buffer}.
33   *
34   * <pre>
35   *     public class IntegerEncoder extends {@link MessageToByteEncoder}&lt;{@link Integer}&gt; {
36   *         {@code @Override}
37   *         public void encode({@link ChannelHandlerContext} ctx, {@link Integer} msg, {@link Buffer} out)
38   *                 throws {@link Exception} {
39   *             out.writeInt(msg);
40   *         }
41   *     }
42   * </pre>
43   */
44  public abstract class MessageToByteEncoder<I> extends ChannelHandlerAdapter {
45  
46      private final TypeParameterMatcher matcher;
47  
48      /**
49       * Create a new instance which will try to detect the types to match out of the type parameter of the class.
50       */
51      protected MessageToByteEncoder() {
52          matcher = TypeParameterMatcher.find(this, MessageToByteEncoder.class, "I");
53      }
54  
55      /**
56       * Create a new instance.
57       *
58       * @param outboundMessageType The type of messages to match.
59       */
60      protected MessageToByteEncoder(Class<? extends I> outboundMessageType) {
61          matcher = TypeParameterMatcher.get(requireNonNull(outboundMessageType, "outboundMessageType"));
62      }
63  
64      /**
65       * Returns {@code true} if the given message should be handled. If {@code false} it will be passed to the next
66       * {@link ChannelHandler} in the {@link ChannelPipeline}.
67       */
68      public boolean acceptOutboundMessage(Object msg) throws Exception {
69          return matcher.match(msg);
70      }
71  
72      @Override
73      public Future<Void> write(ChannelHandlerContext ctx, Object msg) {
74          Buffer buf = null;
75          try {
76              if (acceptOutboundMessage(msg)) {
77                  @SuppressWarnings("unchecked")
78                  I cast = (I) msg;
79                  buf = allocateBuffer(ctx, cast);
80                  try (AutoCloseable ignore = autoClosing(cast)) {
81                      encode(ctx, cast, buf);
82                  }
83  
84                  if (buf.readableBytes() > 0) {
85                      Future<Void> f = ctx.write(buf);
86                      buf = null;
87                      return f;
88                  }
89                  return ctx.write(ctx.bufferAllocator().allocate(0));
90              }
91              return ctx.write(msg);
92          } catch (EncoderException e) {
93              return ctx.newFailedFuture(e);
94          } catch (Throwable e) {
95              return ctx.newFailedFuture(new EncoderException(e));
96          } finally {
97              if (buf != null) {
98                  buf.close();
99              }
100         }
101     }
102 
103     /**
104      * Allocate a {@link Buffer} which will be used as argument of {@link #encode(ChannelHandlerContext, I, Buffer)}.
105      *
106      * @param ctx the {@link ChannelHandlerContext} which this {@link MessageToByteEncoder} belongs to
107      * @param msg the message to be encoded
108      */
109     protected abstract Buffer allocateBuffer(ChannelHandlerContext ctx, I msg) throws Exception;
110 
111     /**
112      * Encode a message into a {@link Buffer}. This method will be called for each written message that can be handled
113      * by this encoder.
114      *
115      * @param ctx the {@link ChannelHandlerContext} which this {@link MessageToByteEncoder} belongs to
116      * @param msg the message to encode
117      * @param out the {@link Buffer} into which the encoded message will be written
118      * @throws Exception is thrown if an error occurs
119      */
120     protected abstract void encode(ChannelHandlerContext ctx, I msg, Buffer out) throws Exception;
121 }