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    *   http://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.ChannelHandler;
19  import io.netty.channel.ChannelHandlerAdapter;
20  import io.netty.channel.ChannelHandlerContext;
21  import io.netty.channel.ChannelPipeline;
22  import io.netty.channel.ChannelPromise;
23  import io.netty.util.ReferenceCountUtil;
24  import io.netty.util.ReferenceCounted;
25  import io.netty.util.internal.RecyclableArrayList;
26  import io.netty.util.internal.StringUtil;
27  import io.netty.util.internal.TypeParameterMatcher;
28  
29  import java.util.List;
30  
31  /**
32   * A {@link ChannelHandler} which encodes from one message to an other message
33   *
34   * For example here is an implementation which decodes an {@link Integer} to an {@link String}.
35   *
36   * <pre>
37   *     public class IntegerToStringEncoder extends
38   *             {@link MessageToMessageEncoder}&lt;{@link Integer}&gt; {
39   *
40   *         {@code @Override}
41   *         public void encode({@link ChannelHandlerContext} ctx, {@link Integer} message, List&lt;Object&gt; out)
42   *                 throws {@link Exception} {
43   *             out.add(message.toString());
44   *         }
45   *     }
46   * </pre>
47   *
48   * Be aware that you need to call {@link ReferenceCounted#retain()} on messages that are just passed through if they
49   * are of type {@link ReferenceCounted}. This is needed as the {@link MessageToMessageEncoder} will call
50   * {@link ReferenceCounted#release()} on encoded messages.
51   */
52  public abstract class MessageToMessageEncoder<I> extends ChannelHandlerAdapter {
53  
54      private final TypeParameterMatcher matcher;
55  
56      /**
57       * Create a new instance which will try to detect the types to match out of the type parameter of the class.
58       */
59      protected MessageToMessageEncoder() {
60          matcher = TypeParameterMatcher.find(this, MessageToMessageEncoder.class, "I");
61      }
62  
63      /**
64       * Create a new instance
65       *
66       * @param outboundMessageType   The type of messages to match and so encode
67       */
68      protected MessageToMessageEncoder(Class<? extends I> outboundMessageType) {
69          matcher = TypeParameterMatcher.get(outboundMessageType);
70      }
71  
72      /**
73       * Returns {@code true} if the given message should be handled. If {@code false} it will be passed to the next
74       * {@link ChannelHandler} in the {@link ChannelPipeline}.
75       */
76      public boolean acceptOutboundMessage(Object msg) throws Exception {
77          return matcher.match(msg);
78      }
79  
80      @Override
81      public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
82          RecyclableArrayList out = null;
83          try {
84              if (acceptOutboundMessage(msg)) {
85                  out = RecyclableArrayList.newInstance();
86                  @SuppressWarnings("unchecked")
87                  I cast = (I) msg;
88                  try {
89                      encode(ctx, cast, out);
90                  } finally {
91                      ReferenceCountUtil.release(cast);
92                  }
93  
94                  if (out.isEmpty()) {
95                      out.recycle();
96                      out = null;
97  
98                      throw new EncoderException(
99                              StringUtil.simpleClassName(this) + " must produce at least one message.");
100                 }
101             } else {
102                 ctx.write(msg, promise);
103             }
104         } catch (EncoderException e) {
105             throw e;
106         } catch (Throwable t) {
107             throw new EncoderException(t);
108         } finally {
109             if (out != null) {
110                 final int sizeMinusOne = out.size() - 1;
111                 if (sizeMinusOne == 0) {
112                     ctx.write(out.get(0), promise);
113                 } else if (sizeMinusOne > 0) {
114                     // Check if we can use a voidPromise for our extra writes to reduce GC-Pressure
115                     // See https://github.com/netty/netty/issues/2525
116                     ChannelPromise voidPromise = ctx.voidPromise();
117                     boolean isVoidPromise = promise == voidPromise;
118                     for (int i = 0; i < sizeMinusOne; i ++) {
119                         ChannelPromise p;
120                         if (isVoidPromise) {
121                             p = voidPromise;
122                         } else {
123                             p = ctx.newPromise();
124                         }
125                         ctx.write(out.get(i), p);
126                     }
127                     ctx.write(out.get(sizeMinusOne), promise);
128                 }
129                 out.recycle();
130             }
131         }
132     }
133 
134     /**
135      * Encode from one message to an other. This method will be called for each written message that can be handled
136      * by this encoder.
137      *
138      * @param ctx           the {@link ChannelHandlerContext} which this {@link MessageToMessageEncoder} belongs to
139      * @param msg           the message to encode to an other one
140      * @param out           the {@link List} into which the encoded msg should be added
141      *                      needs to do some kind of aggragation
142      * @throws Exception    is thrown if an error accour
143      */
144     protected abstract void encode(ChannelHandlerContext ctx, I msg, List<Object> out) throws Exception;
145 }