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.netty.handler.codec;
17  
18  import io.netty.channel.ChannelHandlerContext;
19  import io.netty.channel.ChannelInboundHandler;
20  import io.netty.channel.ChannelInboundHandlerAdapter;
21  import io.netty.channel.ChannelPipeline;
22  import io.netty.util.ReferenceCountUtil;
23  import io.netty.util.ReferenceCounted;
24  import io.netty.util.internal.TypeParameterMatcher;
25  
26  import java.util.List;
27  
28  /**
29   * {@link ChannelInboundHandlerAdapter} which decodes from one message to an other message.
30   *
31   *
32   * For example here is an implementation which decodes a {@link String} to an {@link Integer} which represent
33   * the length of the {@link String}.
34   *
35   * <pre>
36   *     public class StringToIntegerDecoder extends
37   *             {@link MessageToMessageDecoder}&lt;{@link String}&gt; {
38   *
39   *         {@code @Override}
40   *         public void decode({@link ChannelHandlerContext} ctx, {@link String} message,
41   *                            List&lt;Object&gt; out) throws {@link Exception} {
42   *             out.add(message.length());
43   *         }
44   *     }
45   * </pre>
46   *
47   * Be aware that you need to call {@link ReferenceCounted#retain()} on messages that are just passed through if they
48   * are of type {@link ReferenceCounted}. This is needed as the {@link MessageToMessageDecoder} will call
49   * {@link ReferenceCounted#release()} on decoded messages.
50   *
51   */
52  public abstract class MessageToMessageDecoder<I> extends ChannelInboundHandlerAdapter {
53  
54      private final TypeParameterMatcher matcher;
55      private boolean decodeCalled;
56      private boolean messageProduced;
57  
58      /**
59       * Create a new instance which will try to detect the types to match out of the type parameter of the class.
60       */
61      protected MessageToMessageDecoder() {
62          matcher = TypeParameterMatcher.find(this, MessageToMessageDecoder.class, "I");
63      }
64  
65      /**
66       * Create a new instance
67       *
68       * @param inboundMessageType    The type of messages to match and so decode
69       */
70      protected MessageToMessageDecoder(Class<? extends I> inboundMessageType) {
71          matcher = TypeParameterMatcher.get(inboundMessageType);
72      }
73  
74      /**
75       * Returns {@code true} if the given message should be handled. If {@code false} it will be passed to the next
76       * {@link ChannelInboundHandler} in the {@link ChannelPipeline}.
77       */
78      public boolean acceptInboundMessage(Object msg) throws Exception {
79          return matcher.match(msg);
80      }
81  
82      @Override
83      public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
84          decodeCalled = true;
85          CodecOutputList out = CodecOutputList.newInstance();
86          try {
87              if (acceptInboundMessage(msg)) {
88                  @SuppressWarnings("unchecked")
89                  I cast = (I) msg;
90                  try {
91                      decode(ctx, cast, out);
92                  } finally {
93                      ReferenceCountUtil.release(cast);
94                  }
95              } else {
96                  out.add(msg);
97              }
98          } catch (DecoderException e) {
99              throw e;
100         } catch (Exception e) {
101             throw new DecoderException(e);
102         } finally {
103             try {
104                 int size = out.size();
105                 messageProduced |= size > 0;
106                 for (int i = 0; i < size; i++) {
107                     ctx.fireChannelRead(out.getUnsafe(i));
108                 }
109             } finally {
110                 out.recycle();
111             }
112         }
113     }
114 
115     @Override
116     public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
117         if (!isSharable()) {
118             // Only use local vars if this decoder is not sharable as otherwise this is not safe to do.
119             if (decodeCalled && !messageProduced && !ctx.channel().config().isAutoRead()) {
120                 ctx.read();
121             }
122             decodeCalled = false;
123             messageProduced = false;
124         }
125         ctx.fireChannelReadComplete();
126     }
127 
128     /**
129      * Decode from one message to an other. This method will be called for each written message that can be handled
130      * by this decoder.
131      *
132      * @param ctx           the {@link ChannelHandlerContext} which this {@link MessageToMessageDecoder} belongs to
133      * @param msg           the message to decode to an other one
134      * @param out           the {@link List} to which decoded messages should be added
135      * @throws Exception    is thrown if an error occurs
136      */
137     protected abstract void decode(ChannelHandlerContext ctx, I msg, List<Object> out) throws Exception;
138 }