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.ChannelHandlerContext;
19 import io.netty.channel.ChannelOutboundHandler;
20 import io.netty.channel.ChannelOutboundHandlerAdapter;
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.concurrent.PromiseCombiner;
26 import io.netty.util.internal.StringUtil;
27 import io.netty.util.internal.TypeParameterMatcher;
28
29 import java.util.List;
30
31 /**
32  * {@link ChannelOutboundHandlerAdapter} 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 ChannelOutboundHandlerAdapter {
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 trueif the given message should be handled. If {@code false} it will be passed to the next
74      * {@link ChannelOutboundHandler} 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         CodecOutputList out = null;
83         try {
84             if (acceptOutboundMessage(msg)) {
85                 out = CodecOutputList.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                     throw new EncoderException(
96                             StringUtil.simpleClassName(this) + " must produce at least one message.");
97                 }
98             } else {
99                 ctx.write(msg, promise);
100             }
101         } catch (EncoderException e) {
102             throw e;
103         } catch (Throwable t) {
104             throw new EncoderException(t);
105         } finally {
106             if (out != null) {
107                 try {
108                     final int sizeMinusOne = out.size() - 1;
109                     if (sizeMinusOne == 0) {
110                         ctx.write(out.getUnsafe(0), promise);
111                     } else if (sizeMinusOne > 0) {
112                         // Check if we can use a voidPromise for our extra writes to reduce GC-Pressure
113                         // See https://github.com/netty/netty/issues/2525
114                         if (promise == ctx.voidPromise()) {
115                             writeVoidPromise(ctx, out);
116                         } else {
117                             writePromiseCombiner(ctx, out, promise);
118                         }
119                     }
120                 } finally {
121                     out.recycle();
122                 }
123             }
124         }
125     }
126
127     private static void writeVoidPromise(ChannelHandlerContext ctx, CodecOutputList out) {
128         final ChannelPromise voidPromise = ctx.voidPromise();
129         for (int i = 0; i < out.size(); i++) {
130             ctx.write(out.getUnsafe(i), voidPromise);
131         }
132     }
133
134     private static void writePromiseCombiner(ChannelHandlerContext ctx, CodecOutputList out, ChannelPromise promise) {
135         final PromiseCombiner combiner = new PromiseCombiner(ctx.executor());
136         for (int i = 0; i < out.size(); i++) {
137             combiner.add(ctx.write(out.getUnsafe(i)));
138         }
139         combiner.finish(promise);
140     }
141
142     /**
143      * Encode from one message to an other. This method will be called for each written message that can be handled
144      * by this encoder.
145      *
146      * @param ctx           the {@link ChannelHandlerContext} which this {@link MessageToMessageEncoder} belongs to
147      * @param msg           the message to encode to an other one
148      * @param out           the {@link List} into which the encoded msg should be added
149      *                      needs to do some kind of aggregation
150      * @throws Exception    is thrown if an error occurs
151      */

152     protected abstract void encode(ChannelHandlerContext ctx, I msg, List<Object> out) throws Exception;
153 }
154