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    *   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.channel.ChannelHandler;
19  import io.netty5.channel.ChannelHandlerAdapter;
20  import io.netty5.channel.ChannelHandlerContext;
21  import io.netty5.channel.ChannelPipeline;
22  import io.netty5.util.Resource;
23  import io.netty5.util.internal.TypeParameterMatcher;
24  
25  import static io.netty5.util.internal.SilentDispose.autoClosing;
26  
27  /**
28   * {@link ChannelHandler} which decodes from one message to another message.
29   *
30   * For example here is an implementation which decodes a {@link String} to an {@link Integer} which represent
31   * the length of the {@link String}.
32   *
33   * <pre>{@code
34   *     public class StringToIntegerDecoder extends
35   *             MessageToMessageDecoder<String> {
36   *
37   *         @Override
38   *         public void decode(ChannelHandlerContext ctx, String message,
39   *                            List<Object> out) throws Exception {
40   *             out.add(message.length());
41   *         }
42   *     }
43   * }</pre>
44   *
45   * Note that messages passed to {@link #decode(ChannelHandlerContext, Object)} will be
46   * {@linkplain Resource#dispose(Object) disposed of} automatically.
47   * <p>
48   * To take control of the message lifetime, you should instead override the
49   * {@link #decodeAndClose(ChannelHandlerContext, Object)} method.
50   * <p>
51   * Do not override both.
52   */
53  public abstract class MessageToMessageDecoder<I> extends ChannelHandlerAdapter {
54  
55      private final TypeParameterMatcher matcher;
56  
57      /**
58       * Create a new instance which will try to detect the types to match out of the type parameter of the class.
59       */
60      protected MessageToMessageDecoder() {
61          matcher = TypeParameterMatcher.find(this, MessageToMessageDecoder.class, "I");
62      }
63  
64      /**
65       * Create a new instance
66       *
67       * @param inboundMessageType    The type of messages to match and so decode
68       */
69      protected MessageToMessageDecoder(Class<? extends I> inboundMessageType) {
70          matcher = TypeParameterMatcher.get(inboundMessageType);
71      }
72  
73      /**
74       * Returns {@code true} if the given message should be handled. If {@code false} it will be passed to the next
75       * {@link ChannelHandler} in the {@link ChannelPipeline}.
76       */
77      public boolean acceptInboundMessage(Object msg) throws Exception {
78          return matcher.match(msg);
79      }
80  
81      @Override
82      public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
83          try {
84              if (acceptInboundMessage(msg)) {
85                  @SuppressWarnings("unchecked")
86                  I cast = (I) msg;
87                  decodeAndClose(ctx, cast);
88              } else {
89                  ctx.fireChannelRead(msg);
90              }
91          } catch (DecoderException e) {
92              throw e;
93          } catch (Exception e) {
94              throw new DecoderException(e);
95          }
96      }
97  
98      /**
99       * Decode from one message to another. This method will be called for each written message that can be handled
100      * by this decoder.
101      * <p>
102      * The message will be {@linkplain Resource#dispose(Object) disposed of} after this call.
103      * <p>
104      * Subclasses that wish to sometimes pass messages through, should instead override the
105      * {@link #decodeAndClose(ChannelHandlerContext, Object)} method.
106      *
107      * @param ctx           the {@link ChannelHandlerContext} which this {@link MessageToMessageDecoder} belongs to
108      * @param msg           the message to decode to another one
109      * @throws Exception    is thrown if an error occurs
110      */
111     protected void decode(ChannelHandlerContext ctx, I msg) throws Exception {
112         throw new CodecException(getClass().getName() + " must override either decode() or decodeAndClose().");
113     }
114 
115     /**
116      * Decode from one message to another. This method will be called for each written message that can be handled
117      * by this decoder.
118      * <p>
119      * The message will not be automatically {@linkplain Resource#dispose(Object) disposed of} after this call.
120      * Instead, the responsibility of ensuring that messages are disposed of falls upon the implementor of this method.
121      * <p>
122      * Subclasses that wish to have incoming messages automatically disposed of should instead override the
123      * {@link #decodeAndClose(ChannelHandlerContext, Object)} method.
124      *
125      * @param ctx           the {@link ChannelHandlerContext} which this {@link MessageToMessageDecoder} belongs to
126      * @param msg           the message to decode to another one
127      * @throws Exception    is thrown if an error occurs
128      */
129     protected void decodeAndClose(ChannelHandlerContext ctx, I msg) throws Exception {
130         try (AutoCloseable ignore = autoClosing(msg)) {
131             decode(ctx, msg);
132         }
133     }
134 }