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.concurrent.Future;
24  import io.netty5.util.concurrent.Promise;
25  import io.netty5.util.concurrent.PromiseCombiner;
26  import io.netty5.util.internal.StringUtil;
27  import io.netty5.util.internal.TypeParameterMatcher;
28  
29  import java.util.List;
30  
31  import static io.netty5.util.internal.SilentDispose.autoClosing;
32  
33  /**
34   * {@link ChannelHandler} which encodes from one message to another message
35   *
36   * For example here is an implementation which decodes an {@link Integer} to an {@link String}.
37   *
38   * <pre>{@code
39   *     public class IntegerToStringEncoder extends
40   *             MessageToMessageEncoder<Integer> {
41   *
42   *         @Override
43   *         public void encode(ChannelHandlerContext ctx, Integer message, List<Object> out)
44   *                 throws Exception {
45   *             out.add(message.toString());
46   *         }
47   *     }
48   * }</pre>
49   *
50   * Note that messages passed to {@link #encode(ChannelHandlerContext, Object, List)} will be
51   * {@linkplain Resource#dispose(Object) disposed of} automatically.
52   * <p>
53   * To take control of the message lifetime, you should instead override the
54   * {@link #encodeAndClose(ChannelHandlerContext, Object, List)} method.
55   * <p>
56   * Do not override both.
57   */
58  public abstract class MessageToMessageEncoder<I> extends ChannelHandlerAdapter {
59  
60      private final TypeParameterMatcher matcher;
61  
62      /**
63       * Create a new instance which will try to detect the types to match out of the type parameter of the class.
64       */
65      protected MessageToMessageEncoder() {
66          matcher = TypeParameterMatcher.find(this, MessageToMessageEncoder.class, "I");
67      }
68  
69      /**
70       * Create a new instance
71       *
72       * @param outboundMessageType   The type of messages to match and so encode
73       */
74      protected MessageToMessageEncoder(Class<? extends I> outboundMessageType) {
75          matcher = TypeParameterMatcher.get(outboundMessageType);
76      }
77  
78      /**
79       * Returns {@code true} if the given message should be handled. If {@code false} it will be passed to the next
80       * {@link ChannelHandler} in the {@link ChannelPipeline}.
81       */
82      public boolean acceptOutboundMessage(Object msg) throws Exception {
83          return matcher.match(msg);
84      }
85  
86      @Override
87      public Future<Void> write(ChannelHandlerContext ctx, Object msg) {
88          CodecOutputList out = null;
89          try {
90              if (acceptOutboundMessage(msg)) {
91                  out = CodecOutputList.newInstance();
92                  @SuppressWarnings("unchecked")
93                  I cast = (I) msg;
94                  Promise<Void> promise = ctx.newPromise();
95                  try {
96                      encodeAndClose(ctx, cast, out);
97  
98                      if (out.isEmpty()) {
99                          throw new EncoderException(
100                                 StringUtil.simpleClassName(this) + " must produce at least one message.");
101                     }
102                 } finally {
103                     if (out.size() == 1) {
104                         ctx.write(out.getUnsafe(0)).cascadeTo(promise);
105                     } else {
106                         writePromiseCombiner(ctx, out, promise);
107                     }
108                 }
109                 return promise.asFuture();
110             } else {
111                 return ctx.write(msg);
112             }
113         } catch (EncoderException e) {
114             return ctx.newFailedFuture(e);
115         } catch (Throwable t) {
116             return ctx.newFailedFuture(new EncoderException(
117                     "Unhandled exception in encoder " + getClass().getName(), t));
118         } finally {
119             if (out != null) {
120                 out.recycle();
121             }
122         }
123     }
124 
125     private static void writePromiseCombiner(ChannelHandlerContext ctx, CodecOutputList out, Promise<Void> promise) {
126         final PromiseCombiner combiner = new PromiseCombiner(ctx.executor());
127         for (int i = 0; i < out.size(); i++) {
128             combiner.add(ctx.write(out.getUnsafe(i)));
129         }
130         combiner.finish(promise);
131     }
132 
133     /**
134      * Encode from one message to another. This method will be called for each written message that can be handled
135      * by this encoder.
136      * <p>
137      * The message will be {@linkplain Resource#dispose(Object) disposed of} after this call.
138      * <p>
139      * Subclasses that wish to sometimes pass messages through, should instead override the
140      * {@link #encodeAndClose(ChannelHandlerContext, Object, List)} method.
141      *
142      * @param ctx           the {@link ChannelHandlerContext} which this {@link MessageToMessageEncoder} belongs to.
143      * @param msg           the message to encode to another one.
144      * @param out           the {@link List} into which produced output messages should be added.
145      * @throws Exception    is thrown if an error occurs.
146      */
147     protected void encode(ChannelHandlerContext ctx, I msg, List<Object> out) throws Exception {
148         throw new CodecException(getClass().getName() + " must override either encode() or encodeAndClose().");
149     }
150 
151     /**
152      * Encode from one message to another. This method will be called for each written message that can be handled
153      * by this encoder.
154      * <p>
155      * The message will not be automatically {@linkplain Resource#dispose(Object) disposed of} after this call.
156      * Instead, the responsibility of ensuring that messages are disposed of falls upon the implementor of this method.
157      * <p>
158      * Subclasses that wish to have incoming messages automatically disposed of should instead override the
159      * {@link #encode(ChannelHandlerContext, Object, List)} method.
160      *
161      * @param ctx           the {@link ChannelHandlerContext} which this {@link MessageToMessageEncoder} belongs to.
162      * @param msg           the message to encode to another one.
163      * @param out           the {@link List} into which produced output messages should be added.
164      * @throws Exception    is thrown if an error occurs.
165      */
166     protected void encodeAndClose(ChannelHandlerContext ctx, I msg, List<Object> out) throws Exception {
167         try (AutoCloseable ignore = autoClosing(msg)) {
168             encode(ctx, msg, out);
169         }
170     }
171 }