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.buffer.ByteBuf;
19  import io.netty.buffer.Unpooled;
20  import io.netty.channel.ChannelHandlerContext;
21  import io.netty.channel.ChannelOutboundHandler;
22  import io.netty.channel.ChannelOutboundHandlerAdapter;
23  import io.netty.channel.ChannelPipeline;
24  import io.netty.channel.ChannelPromise;
25  import io.netty.util.ReferenceCountUtil;
26  import io.netty.util.internal.TypeParameterMatcher;
27  
28  
29  /**
30   * {@link ChannelOutboundHandlerAdapter} which encodes message in a stream-like fashion from one message to an
31   * {@link ByteBuf}.
32   *
33   *
34   * Example implementation which encodes {@link Integer}s to a {@link ByteBuf}.
35   *
36   * <pre>
37   *     public class IntegerEncoder extends {@link MessageToByteEncoder}&lt;{@link Integer}&gt; {
38   *         {@code @Override}
39   *         public void encode({@link ChannelHandlerContext} ctx, {@link Integer} msg, {@link ByteBuf} out)
40   *                 throws {@link Exception} {
41   *             out.writeInt(msg);
42   *         }
43   *     }
44   * </pre>
45   */
46  public abstract class MessageToByteEncoder<I> extends ChannelOutboundHandlerAdapter {
47  
48      private final TypeParameterMatcher matcher;
49      private final boolean preferDirect;
50  
51      /**
52       * see {@link #MessageToByteEncoder(boolean)} with {@code true} as boolean parameter.
53       */
54      protected MessageToByteEncoder() {
55          this(true);
56      }
57  
58      /**
59       * see {@link #MessageToByteEncoder(Class, boolean)} with {@code true} as boolean value.
60       */
61      protected MessageToByteEncoder(Class<? extends I> outboundMessageType) {
62          this(outboundMessageType, true);
63      }
64  
65      /**
66       * Create a new instance which will try to detect the types to match out of the type parameter of the class.
67       *
68       * @param preferDirect          {@code true} if a direct {@link ByteBuf} should be tried to be used as target for
69       *                              the encoded messages. If {@code false} is used it will allocate a heap
70       *                              {@link ByteBuf}, which is backed by an byte array.
71       */
72      protected MessageToByteEncoder(boolean preferDirect) {
73          matcher = TypeParameterMatcher.find(this, MessageToByteEncoder.class, "I");
74          this.preferDirect = preferDirect;
75      }
76  
77      /**
78       * Create a new instance
79       *
80       * @param outboundMessageType   The type of messages to match
81       * @param preferDirect          {@code true} if a direct {@link ByteBuf} should be tried to be used as target for
82       *                              the encoded messages. If {@code false} is used it will allocate a heap
83       *                              {@link ByteBuf}, which is backed by an byte array.
84       */
85      protected MessageToByteEncoder(Class<? extends I> outboundMessageType, boolean preferDirect) {
86          matcher = TypeParameterMatcher.get(outboundMessageType);
87          this.preferDirect = preferDirect;
88      }
89  
90      /**
91       * Returns {@code true} if the given message should be handled. If {@code false} it will be passed to the next
92       * {@link ChannelOutboundHandler} in the {@link ChannelPipeline}.
93       */
94      public boolean acceptOutboundMessage(Object msg) throws Exception {
95          return matcher.match(msg);
96      }
97  
98      @Override
99      public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
100         ByteBuf buf = null;
101         try {
102             if (acceptOutboundMessage(msg)) {
103                 @SuppressWarnings("unchecked")
104                 I cast = (I) msg;
105                 buf = allocateBuffer(ctx, cast, preferDirect);
106                 try {
107                     encode(ctx, cast, buf);
108                 } finally {
109                     ReferenceCountUtil.release(cast);
110                 }
111 
112                 if (buf.isReadable()) {
113                     ctx.write(buf, promise);
114                 } else {
115                     buf.release();
116                     ctx.write(Unpooled.EMPTY_BUFFER, promise);
117                 }
118                 buf = null;
119             } else {
120                 ctx.write(msg, promise);
121             }
122         } catch (EncoderException e) {
123             throw e;
124         } catch (Throwable e) {
125             throw new EncoderException(e);
126         } finally {
127             if (buf != null) {
128                 buf.release();
129             }
130         }
131     }
132 
133     /**
134      * Allocate a {@link ByteBuf} which will be used as argument of {@link #encode(ChannelHandlerContext, I, ByteBuf)}.
135      * Sub-classes may override this method to return {@link ByteBuf} with a perfect matching {@code initialCapacity}.
136      */
137     protected ByteBuf allocateBuffer(ChannelHandlerContext ctx, @SuppressWarnings("unused") I msg,
138                                boolean preferDirect) throws Exception {
139         if (preferDirect) {
140             return ctx.alloc().ioBuffer();
141         } else {
142             return ctx.alloc().heapBuffer();
143         }
144     }
145 
146     /**
147      * Encode a message into a {@link ByteBuf}. This method will be called for each written message that can be handled
148      * by this encoder.
149      *
150      * @param ctx           the {@link ChannelHandlerContext} which this {@link MessageToByteEncoder} belongs to
151      * @param msg           the message to encode
152      * @param out           the {@link ByteBuf} into which the encoded message will be written
153      * @throws Exception    is thrown if an error occurs
154      */
155     protected abstract void encode(ChannelHandlerContext ctx, I msg, ByteBuf out) throws Exception;
156 
157     protected boolean isPreferDirect() {
158         return preferDirect;
159     }
160 }